mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-06-04 23:08:42 +01:00
Compare commits
914 Commits
v10.8.0-al
...
v10.8.0-be
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d45d228b36 | ||
|
|
a280ff603f | ||
|
|
9beb3aff4e | ||
|
|
066bdc1e72 | ||
|
|
5833c70725 | ||
|
|
cd93f49fa8 | ||
|
|
93009682b3 | ||
|
|
9001eaa67e | ||
|
|
76010e80dd | ||
|
|
5778541d2f | ||
|
|
cb0baddde3 | ||
|
|
0ff37413b0 | ||
|
|
d5434988d7 | ||
|
|
847518701d | ||
|
|
c5212a20a3 | ||
|
|
385a0b9437 | ||
|
|
884ba4f3ed | ||
|
|
cba6a4e3f3 | ||
|
|
d5f44f7a5c | ||
|
|
bf1ccf7493 | ||
|
|
d7c548f3db | ||
|
|
a7abdca47a | ||
|
|
7a8eaa8811 | ||
|
|
045dca49d0 | ||
|
|
e9ce82445e | ||
|
|
2133c6e348 | ||
|
|
5de2db9f52 | ||
|
|
620625c4c1 | ||
|
|
e0b035e34e | ||
|
|
1946414e14 | ||
|
|
132c85e554 | ||
|
|
9d1049e83f | ||
|
|
72aca15191 | ||
|
|
577325b788 | ||
|
|
fec2cf5060 | ||
|
|
53b06ce4e3 | ||
|
|
bdb85aeecf | ||
|
|
e299adc819 | ||
|
|
0674f84e9e | ||
|
|
b6086398d3 | ||
|
|
1d585146d6 | ||
|
|
aa1b1c6bbb | ||
|
|
bebe1808ce | ||
|
|
fb2e4c2e1a | ||
|
|
784ed796ce | ||
|
|
579155a571 | ||
|
|
ca16a55a47 | ||
|
|
e2ffd41141 | ||
|
|
ca67a48140 | ||
|
|
d2ce315c1d | ||
|
|
6a6874aa16 | ||
|
|
6f45848b51 | ||
|
|
23ba15ccfb | ||
|
|
5376c37d42 | ||
|
|
a0eb1fd28b | ||
|
|
130c935bc3 | ||
|
|
003c48e351 | ||
|
|
96a03776e1 | ||
|
|
ac33adf219 | ||
|
|
4fa7f49ffc | ||
|
|
0a068b924f | ||
|
|
d80c4ee381 | ||
|
|
e6014bb8d3 | ||
|
|
8056b0e961 | ||
|
|
6a7775de6e | ||
|
|
4e91c3ebdc | ||
|
|
78a8c26690 | ||
|
|
c229f3ae0a | ||
|
|
3cea6c61f3 | ||
|
|
8197e5921e | ||
|
|
5f7ffd7863 | ||
|
|
c28025ba5e | ||
|
|
af5200857e | ||
|
|
97d3212410 | ||
|
|
8780fc0ea1 | ||
|
|
570660099b | ||
|
|
945fb8b72e | ||
|
|
16a449a023 | ||
|
|
669029595b | ||
|
|
72cfa6d7c9 | ||
|
|
7fb21132dd | ||
|
|
3bf5a7b801 | ||
|
|
74bdad82c1 | ||
|
|
c59087606c | ||
|
|
d7d36a102a | ||
|
|
b4bb82b6d7 | ||
|
|
eb44f892b8 | ||
|
|
90463d5e4f | ||
|
|
6904edf68c | ||
|
|
dc8fdb154a | ||
|
|
c512e783b3 | ||
|
|
4398475aac | ||
|
|
fb8ae0e9a1 | ||
|
|
5d44e45b90 | ||
|
|
dde984bd0e | ||
|
|
ba11add83b | ||
|
|
e86e64807d | ||
|
|
320ab8db6a | ||
|
|
60e0becc54 | ||
|
|
acd4a97e65 | ||
|
|
009e3a6c72 | ||
|
|
f8b5631d62 | ||
|
|
8871e5af4b | ||
|
|
4c117b0259 | ||
|
|
e4137a6279 | ||
|
|
1fe82d0deb | ||
|
|
588c349eb4 | ||
|
|
b9c3a497d5 | ||
|
|
dd6845b8f3 | ||
|
|
464ebf93dd | ||
|
|
93f569d286 | ||
|
|
0e2b20e6d6 | ||
|
|
e1527857e9 | ||
|
|
6a567e8c76 | ||
|
|
4b5148c69e | ||
|
|
8a827ba995 | ||
|
|
bb7068cb63 | ||
|
|
46798cb918 | ||
|
|
5ece92d635 | ||
|
|
9fbd675bed | ||
|
|
5cf0c1b8f8 | ||
|
|
c8a8a85944 | ||
|
|
9f147216be | ||
|
|
db60c9da25 | ||
|
|
75475285da | ||
|
|
18c7dc210c | ||
|
|
ca517af0d9 | ||
|
|
fccf3ac3eb | ||
|
|
1d7a524d82 | ||
|
|
6a3da3a031 | ||
|
|
456d95c878 | ||
|
|
36040832cc | ||
|
|
cb91ad184e | ||
|
|
122f6acb15 | ||
|
|
b4913aad30 | ||
|
|
6fa38e300a | ||
|
|
fbbb0368f7 | ||
|
|
67a0a998cc | ||
|
|
95e97fea01 | ||
|
|
cf47ee6261 | ||
|
|
d29c7c1d9e | ||
|
|
72165ae96f | ||
|
|
34af6c48d7 | ||
|
|
e6df698df1 | ||
|
|
8ddcdc7430 | ||
|
|
d659b9b9ab | ||
|
|
6bb50d5728 | ||
|
|
3ea4174d12 | ||
|
|
5319f1571f | ||
|
|
6e80c9b25f | ||
|
|
7fdc0e3c3d | ||
|
|
28223704f3 | ||
|
|
edeb198313 | ||
|
|
f671eeb6b2 | ||
|
|
3342c29f4f | ||
|
|
b2188c8676 | ||
|
|
53209830e7 | ||
|
|
21ef6661d6 | ||
|
|
6f25291931 | ||
|
|
1a307db7eb | ||
|
|
a5ffde0e9c | ||
|
|
ac83effd44 | ||
|
|
96de01ce01 | ||
|
|
470d175f32 | ||
|
|
388e0cba9f | ||
|
|
11fc84eebe | ||
|
|
1e80ba0462 | ||
|
|
a397cec981 | ||
|
|
b2c58338f2 | ||
|
|
965bf7332f | ||
|
|
dacbfc83ff | ||
|
|
1d018c5575 | ||
|
|
16e0d46771 | ||
|
|
4f1efb3996 | ||
|
|
c5ca29d2e2 | ||
|
|
03f1eff21a | ||
|
|
e3ea899bcc | ||
|
|
88c4e38ce2 | ||
|
|
ea7e6aeae0 | ||
|
|
f48c8d4802 | ||
|
|
0ef35286c5 | ||
|
|
1ec3e899d6 | ||
|
|
023eddbef5 | ||
|
|
10d1c7b25d | ||
|
|
de766e687d | ||
|
|
bb83e9f2d9 | ||
|
|
4c4f5dfe81 | ||
|
|
da41cd365c | ||
|
|
dbea7cac67 | ||
|
|
bbd5d11d3b | ||
|
|
c331e11c24 | ||
|
|
9ebd521754 | ||
|
|
84a3db6f84 | ||
|
|
5e779f20ee | ||
|
|
d871dded9f | ||
|
|
a3057afde8 | ||
|
|
27b81a535c | ||
|
|
1dcd537ea2 | ||
|
|
0a8bec1af4 | ||
|
|
973781c482 | ||
|
|
b37052a4a6 | ||
|
|
15cae4ef00 | ||
|
|
7af1cae883 | ||
|
|
178d00b14d | ||
|
|
bc3cb04c0b | ||
|
|
2579b2db56 | ||
|
|
3dc0cfc36e | ||
|
|
4791d56f6c | ||
|
|
b705ace262 | ||
|
|
947ff9defe | ||
|
|
414004f83d | ||
|
|
e3d9c53baa | ||
|
|
a123391e7e | ||
|
|
9f027bae41 | ||
|
|
5eda32980c | ||
|
|
76019d3a34 | ||
|
|
3bf83025f2 | ||
|
|
bfc27e494b | ||
|
|
f7118bebfd | ||
|
|
fab5f37e0e | ||
|
|
de4a084b03 | ||
|
|
3f6a14e1fd | ||
|
|
dad7a6fdf6 | ||
|
|
3205e97e1e | ||
|
|
136eab9b1e | ||
|
|
46ef68d589 | ||
|
|
f712c554ab | ||
|
|
e58f7ae5da | ||
|
|
92fd6475fa | ||
|
|
8121635d8c | ||
|
|
9025cd13c5 | ||
|
|
2ce0389d59 | ||
|
|
957199dcba | ||
|
|
73805e0b4a | ||
|
|
f3de108617 | ||
|
|
94793e6c6b | ||
|
|
e09641b452 | ||
|
|
b356c2b212 | ||
|
|
2dbb32976c | ||
|
|
6e91657f01 | ||
|
|
1b3e56bae3 | ||
|
|
df70d7bdf1 | ||
|
|
d16bdfbc05 | ||
|
|
fe53243cfb | ||
|
|
6498a5baca | ||
|
|
71a9ae3150 | ||
|
|
4239f80c81 | ||
|
|
f0c28019dc | ||
|
|
ba8e478a2f | ||
|
|
0a953aca8a | ||
|
|
7d226e8eef | ||
|
|
eff3d3e67e | ||
|
|
ef99a53abb | ||
|
|
1e98f168c9 | ||
|
|
2bc8383968 | ||
|
|
055c63bdee | ||
|
|
7c9aa63382 | ||
|
|
5e1f956fe0 | ||
|
|
89c29a7ed8 | ||
|
|
fe99800bde | ||
|
|
3c0c7572ef | ||
|
|
3f441a44ba | ||
|
|
aec1ee5869 | ||
|
|
c0b02ff0e5 | ||
|
|
dc1b224667 | ||
|
|
fc5c6c0404 | ||
|
|
f32b2cb592 | ||
|
|
1d367712bb | ||
|
|
c9d5cfff1d | ||
|
|
847d705969 | ||
|
|
11bb834957 | ||
|
|
40e413d575 | ||
|
|
cd4d51a515 | ||
|
|
f1808f6ae4 | ||
|
|
9251c875b1 | ||
|
|
10282dc444 | ||
|
|
3fb3d7159c | ||
|
|
11092a72a4 | ||
|
|
ee24b85f49 | ||
|
|
acf30e00ce | ||
|
|
354f22d065 | ||
|
|
175ddff169 | ||
|
|
e26446f9c0 | ||
|
|
d7cbb25d0b | ||
|
|
536ad62ea8 | ||
|
|
ce62a4465a | ||
|
|
a9a3f6bf02 | ||
|
|
cf033b25f8 | ||
|
|
15053516f8 | ||
|
|
59040bfa7d | ||
|
|
d1f6a7f497 | ||
|
|
f50a250cd9 | ||
|
|
def8500dd0 | ||
|
|
06f259001c | ||
|
|
bbac59c6d6 | ||
|
|
a61b42f7ef | ||
|
|
554d1b2ca8 | ||
|
|
bbb3117f83 | ||
|
|
6a8a6a1325 | ||
|
|
36cdeaa53c | ||
|
|
a36e34fbd2 | ||
|
|
719b707281 | ||
|
|
f1878c43a4 | ||
|
|
ca5112f45a | ||
|
|
151ddd400d | ||
|
|
48a7d3f48f | ||
|
|
b92e1baa3c | ||
|
|
adad13b865 | ||
|
|
7ccf7e6157 | ||
|
|
9930efec22 | ||
|
|
0aaf2f470a | ||
|
|
23ea14aa27 | ||
|
|
5732e6188c | ||
|
|
5825a0572b | ||
|
|
4ef0099598 | ||
|
|
4acab00963 | ||
|
|
94b5334da5 | ||
|
|
dbf9e49258 | ||
|
|
5d28c5547e | ||
|
|
dbd7be091d | ||
|
|
8b2556adba | ||
|
|
1c14c86b20 | ||
|
|
3cb49d6df0 | ||
|
|
68e7072698 | ||
|
|
69e7deb07d | ||
|
|
8ec71c094a | ||
|
|
8a2ebae74d | ||
|
|
603b6fe173 | ||
|
|
52c61bd06f | ||
|
|
ab40554759 | ||
|
|
b7cab46b4a | ||
|
|
bb7916767c | ||
|
|
a00e6ff426 | ||
|
|
b98cc71c3b | ||
|
|
103a8e69a7 | ||
|
|
75cdfe4daf | ||
|
|
d855b6872a | ||
|
|
fdfcb45c2a | ||
|
|
7885167f54 | ||
|
|
ea7e5e639d | ||
|
|
08e90039b1 | ||
|
|
7649391b9d | ||
|
|
0c89459d5b | ||
|
|
b6489e73ab | ||
|
|
10d52d16a8 | ||
|
|
f3c3402984 | ||
|
|
2b769aae69 | ||
|
|
c16d71562e | ||
|
|
a2127a48ef | ||
|
|
509d66dcb5 | ||
|
|
6c53420fe3 | ||
|
|
6af7d5445f | ||
|
|
f863ca1f2d | ||
|
|
e5701c396a | ||
|
|
488ce51032 | ||
|
|
42724ef411 | ||
|
|
4a3f1a51d2 | ||
|
|
83605affa0 | ||
|
|
91d143d6ee | ||
|
|
29f92724ef | ||
|
|
614abddd64 | ||
|
|
fabe7e1fb9 | ||
|
|
887beee614 | ||
|
|
0c665af124 | ||
|
|
28f4d574f7 | ||
|
|
09b8cde6aa | ||
|
|
ea3d79c0eb | ||
|
|
3c746f2743 | ||
|
|
da49437549 | ||
|
|
26ba99e420 | ||
|
|
e7be01d7a5 | ||
|
|
2e4dd02f76 | ||
|
|
2afcaa6ae1 | ||
|
|
fbd243e315 | ||
|
|
4f1eed862e | ||
|
|
832da133d8 | ||
|
|
2dcb2f8a9f | ||
|
|
e86f778c05 | ||
|
|
cd675475bc | ||
|
|
34ee6d82fb | ||
|
|
a4246648f4 | ||
|
|
ee43b5117d | ||
|
|
a60cb280a3 | ||
|
|
375903b215 | ||
|
|
cd4587b43f | ||
|
|
5d9e1bfcea | ||
|
|
b15ab397c7 | ||
|
|
3aeae150f8 | ||
|
|
8b36bc0ade | ||
|
|
239b516659 | ||
|
|
5c3119cf02 | ||
|
|
1a32153a31 | ||
|
|
47269d5ec6 | ||
|
|
3b075a5802 | ||
|
|
ef0708d876 | ||
|
|
8b706cebef | ||
|
|
e762454787 | ||
|
|
90736ee346 | ||
|
|
000b7ba62b | ||
|
|
f87e780fb5 | ||
|
|
60fe77c089 | ||
|
|
7500c2b28b | ||
|
|
1d7ec1071f | ||
|
|
19b9646d72 | ||
|
|
f11fa59b15 | ||
|
|
d88e39b71d | ||
|
|
54549cd5b5 | ||
|
|
ea1f3fe6bd | ||
|
|
ee32e46dde | ||
|
|
5aa748058e | ||
|
|
112db30ff2 | ||
|
|
3b486fc0ee | ||
|
|
f28384ba30 | ||
|
|
ee46754238 | ||
|
|
5df6058a8e | ||
|
|
61d8d40a4a | ||
|
|
126274c4ea | ||
|
|
3f7bd7b63e | ||
|
|
aeed255bbf | ||
|
|
4df7590e52 | ||
|
|
70751722d2 | ||
|
|
c32db3ea26 | ||
|
|
a19b6a7f61 | ||
|
|
62dc4a79ff | ||
|
|
07e9568de8 | ||
|
|
7ba9a24736 | ||
|
|
b9d4cbf3e8 | ||
|
|
768b76b999 | ||
|
|
a0f248e200 | ||
|
|
f92806c246 | ||
|
|
9a5a079f42 | ||
|
|
6ffa9539bb | ||
|
|
307679afef | ||
|
|
8a36fe7ed5 | ||
|
|
0d335082c8 | ||
|
|
6520ad03f0 | ||
|
|
ecb73168b3 | ||
|
|
4823e68a94 | ||
|
|
88e02c5290 | ||
|
|
05836c8cd3 | ||
|
|
d24683f0ab | ||
|
|
8c3f98f41b | ||
|
|
d5e7e75421 | ||
|
|
54d24ddeaf | ||
|
|
188f9632f8 | ||
|
|
ddc2569258 | ||
|
|
b43f46d5c9 | ||
|
|
620a5d5e83 | ||
|
|
9b1965b48a | ||
|
|
3ea54a8009 | ||
|
|
bd2bec4d4a | ||
|
|
a26509a98a | ||
|
|
ce61dff4aa | ||
|
|
9574d13059 | ||
|
|
565ebbb643 | ||
|
|
4234a657c8 | ||
|
|
3ab0afdc6b | ||
|
|
98962cc21f | ||
|
|
c658a883a2 | ||
|
|
82260e22a2 | ||
|
|
6b4f5a8663 | ||
|
|
f8a6b3b27b | ||
|
|
904efeaddc | ||
|
|
b6b7a89bf2 | ||
|
|
77c615ba42 | ||
|
|
a2b51d551f | ||
|
|
e300b98573 | ||
|
|
75a46a1072 | ||
|
|
6aa37ebb8f | ||
|
|
072526d798 | ||
|
|
4da240d731 | ||
|
|
b5b4e2ea89 | ||
|
|
7d63377194 | ||
|
|
71ebe220f0 | ||
|
|
3eb4bbbb86 | ||
|
|
360fd70fc7 | ||
|
|
b17fe35e2e | ||
|
|
9e0958d822 | ||
|
|
9e23af5636 | ||
|
|
6ad020acb2 | ||
|
|
c70452b4a4 | ||
|
|
045ef4b726 | ||
|
|
68db3be0e7 | ||
|
|
e026ba84c5 | ||
|
|
3fb3ee074a | ||
|
|
0fd4ff4451 | ||
|
|
c934269a6e | ||
|
|
4ba168c8a1 | ||
|
|
dc222b75c5 | ||
|
|
c6a1dcf420 | ||
|
|
fddcaf17e1 | ||
|
|
b9cd9487bd | ||
|
|
fcb8ff7c0f | ||
|
|
e51777fe4b | ||
|
|
1dfbeae045 | ||
|
|
19164378f2 | ||
|
|
c81d2e9dec | ||
|
|
ce66df2c92 | ||
|
|
6d299eed49 | ||
|
|
8952819494 | ||
|
|
f802763e8f | ||
|
|
37061e5b6b | ||
|
|
1c5c0ebb0f | ||
|
|
0c4b5a5ce8 | ||
|
|
65bf393efb | ||
|
|
38d4300309 | ||
|
|
29755c9384 | ||
|
|
608a91162a | ||
|
|
853ef727da | ||
|
|
11d0c6827f | ||
|
|
0765fd568f | ||
|
|
28c2ac9cc0 | ||
|
|
58b9e5af79 | ||
|
|
1725b2ee69 | ||
|
|
25abe479eb | ||
|
|
1b9cb8cca6 | ||
|
|
c3071f8996 | ||
|
|
dc9e1ece29 | ||
|
|
76e640b0b9 | ||
|
|
226a43619f | ||
|
|
53447976f0 | ||
|
|
b2a2bdb088 | ||
|
|
bb713d1931 | ||
|
|
73a5fc6e26 | ||
|
|
019b631d7b | ||
|
|
6f4a0683ed | ||
|
|
5a1b347b83 | ||
|
|
828bf1f0ac | ||
|
|
4ec7488bab | ||
|
|
7ad1527fd0 | ||
|
|
80fce87f7a | ||
|
|
e3a7c9238d | ||
|
|
2ab3bf97ef | ||
|
|
57db188c2e | ||
|
|
2749509f00 | ||
|
|
7bfc6b5679 | ||
|
|
251b9a5235 | ||
|
|
ea8f40e84a | ||
|
|
4441513ca4 | ||
|
|
ebbde383e8 | ||
|
|
78bb581f0c | ||
|
|
a615f87680 | ||
|
|
d594f81f65 | ||
|
|
6804624c15 | ||
|
|
cf29aae690 | ||
|
|
c0ab54f0bd | ||
|
|
60e75f7b70 | ||
|
|
17e6bd5f88 | ||
|
|
42c661e30f | ||
|
|
9f5873b8c4 | ||
|
|
cbfa355e31 | ||
|
|
728a5988b3 | ||
|
|
2e7d173188 | ||
|
|
ec2645c0c0 | ||
|
|
7db753d247 | ||
|
|
d0832c60d4 | ||
|
|
b2d85a02c2 | ||
|
|
4b9c84c52e | ||
|
|
daa76d3d34 | ||
|
|
599fc9e671 | ||
|
|
17f43c8e01 | ||
|
|
634ce40c2f | ||
|
|
b5459f49d3 | ||
|
|
932c2c6665 | ||
|
|
a04ab6b876 | ||
|
|
a8a8ce4e7b | ||
|
|
8c7dd0a691 | ||
|
|
55b429edb7 | ||
|
|
cecfdeeec3 | ||
|
|
076a13abeb | ||
|
|
259bc231d6 | ||
|
|
7774cb2067 | ||
|
|
00211ca056 | ||
|
|
ca4769ab68 | ||
|
|
84ac692312 | ||
|
|
a7a7173cd5 | ||
|
|
c86b064f80 | ||
|
|
05c8834a3a | ||
|
|
9158511017 | ||
|
|
afaff1310f | ||
|
|
b880dc8a4a | ||
|
|
91292b8ea5 | ||
|
|
83a94aa612 | ||
|
|
cb41dda5b3 | ||
|
|
a633a3052f | ||
|
|
e8ae501430 | ||
|
|
db46eaa744 | ||
|
|
71f6326e1a | ||
|
|
91f3ce3109 | ||
|
|
3f3295a5d6 | ||
|
|
76c2775d8c | ||
|
|
cd760943a9 | ||
|
|
ea9fc9f9cc | ||
|
|
d9bc65469f | ||
|
|
077f13ae4c | ||
|
|
a4565da4a9 | ||
|
|
c35b704a83 | ||
|
|
55146334e8 | ||
|
|
232a148d3f | ||
|
|
968c534864 | ||
|
|
f8fcbc88fc | ||
|
|
92448ffabd | ||
|
|
923720c988 | ||
|
|
16bf4a013b | ||
|
|
b3c48e8e78 | ||
|
|
42a7318fd0 | ||
|
|
8ddba55214 | ||
|
|
0dd304f86c | ||
|
|
249a1ca955 | ||
|
|
dea5a3f3bc | ||
|
|
b5d4fdb56e | ||
|
|
e34fd15196 | ||
|
|
7405450da2 | ||
|
|
9a0618552b | ||
|
|
4a58582ad5 | ||
|
|
4c9bd905c6 | ||
|
|
5e8aaa68cf | ||
|
|
543b0127b3 | ||
|
|
87439665d7 | ||
|
|
c3c4dc6839 | ||
|
|
e575d815cc | ||
|
|
ec9cff29df | ||
|
|
0f4da9f635 | ||
|
|
0edf77994a | ||
|
|
38f0e611c8 | ||
|
|
4e03801931 | ||
|
|
250332104b | ||
|
|
0872ede57b | ||
|
|
0120d80b78 | ||
|
|
9e665ff520 | ||
|
|
5c3d0b5510 | ||
|
|
3a4e7fb075 | ||
|
|
fcf5b9b46e | ||
|
|
9a2b88cb1f | ||
|
|
c5569c701c | ||
|
|
32629cd7da | ||
|
|
2c6d6dbbf8 | ||
|
|
bdf9bdb9e6 | ||
|
|
0e8c97ed60 | ||
|
|
843ba4506e | ||
|
|
3699fa43f0 | ||
|
|
296a61cbc4 | ||
|
|
a04f8f5efb | ||
|
|
58f788e8ab | ||
|
|
7ee96a59d3 | ||
|
|
510f92f4c5 | ||
|
|
a90614d194 | ||
|
|
5e8f27d473 | ||
|
|
1f02ab83cb | ||
|
|
2a82f8dc40 | ||
|
|
584cf47ef5 | ||
|
|
3223ef0e49 | ||
|
|
3797879d05 | ||
|
|
8b4a36d6f7 | ||
|
|
4d1b824651 | ||
|
|
fbc79cb4ce | ||
|
|
6d6fb1bba3 | ||
|
|
681bfbd535 | ||
|
|
220443eca1 | ||
|
|
4d446eb614 | ||
|
|
d707a201c9 | ||
|
|
7d86ef6f22 | ||
|
|
eeb8192602 | ||
|
|
de2d292197 | ||
|
|
593b2fd359 | ||
|
|
13ac3e3665 | ||
|
|
5c407dbab6 | ||
|
|
f50b2f7959 | ||
|
|
1acb9e1d45 | ||
|
|
6640f3f782 | ||
|
|
8aef2cb282 | ||
|
|
8bd331f5fa | ||
|
|
ee80602a0d | ||
|
|
03b3f08354 | ||
|
|
65833076db | ||
|
|
e18d966874 | ||
|
|
4cdb590291 | ||
|
|
d47811bdaf | ||
|
|
01a0a4a87c | ||
|
|
87a6fdf847 | ||
|
|
3513f5a84b | ||
|
|
a327b43ab7 | ||
|
|
3f69eeab27 | ||
|
|
dd8b9e9d23 | ||
|
|
6030946d78 | ||
|
|
fde84a1e00 | ||
|
|
3176a4ddd9 | ||
|
|
9cafa2cab4 | ||
|
|
c4c7d7431f | ||
|
|
f2648f3eee | ||
|
|
29095ab390 | ||
|
|
b9b3959223 | ||
|
|
9b71bf8cfe | ||
|
|
5eff80fb8c | ||
|
|
2d77c729f2 | ||
|
|
9d387c542a | ||
|
|
60f4d70cc5 | ||
|
|
bd48b74d5b | ||
|
|
838adaea48 | ||
|
|
46543ead27 | ||
|
|
707e5bab97 | ||
|
|
cf75f99f0e | ||
|
|
fa6f6515a6 | ||
|
|
0e491025ae | ||
|
|
40e05a7993 | ||
|
|
6193fdea69 | ||
|
|
ca2d94ee97 | ||
|
|
99a48554a6 | ||
|
|
120828d8d0 | ||
|
|
9cea773d29 | ||
|
|
a474e4cf3b | ||
|
|
b1025121b8 | ||
|
|
2029076ce8 | ||
|
|
5535b9c01f | ||
|
|
e446e9fde9 | ||
|
|
180e2dc329 | ||
|
|
40be86eec0 | ||
|
|
beafd6eaab | ||
|
|
f6d8c19a7a | ||
|
|
2da7777e6d | ||
|
|
a0df79d8a5 | ||
|
|
01b95cf8e6 | ||
|
|
6bbfcf1906 | ||
|
|
7b50048020 | ||
|
|
a9a53dc657 | ||
|
|
0d8170cedb | ||
|
|
1a35690834 | ||
|
|
c61b9ef05a | ||
|
|
b5b994b22f | ||
|
|
0894a6193f | ||
|
|
9d34d6339a | ||
|
|
bbf1399826 | ||
|
|
d016d483ae | ||
|
|
61b191d345 | ||
|
|
9433072f90 | ||
|
|
a3c5afa443 | ||
|
|
f1862f9b1a | ||
|
|
a68e58556c | ||
|
|
5e91f50c43 | ||
|
|
c1a8385c9c | ||
|
|
9978164438 | ||
|
|
eaa003775f | ||
|
|
e778462955 | ||
|
|
148fcb1bb8 | ||
|
|
757970bfc1 | ||
|
|
c677b4f6b7 | ||
|
|
20b0499b00 | ||
|
|
09f2456919 | ||
|
|
bcd84dedda | ||
|
|
2496c5d589 | ||
|
|
19c9319d2c | ||
|
|
6ef9ac6e53 | ||
|
|
502a5fc932 | ||
|
|
02ce95edaf | ||
|
|
5addb24e9b | ||
|
|
dc27e7618f | ||
|
|
3e1bbdd3d4 | ||
|
|
2ff786fd3d | ||
|
|
2a09d4244c | ||
|
|
beef6f0855 | ||
|
|
80c7119537 | ||
|
|
1df56335ee | ||
|
|
065d3fa837 | ||
|
|
1df5b5034b | ||
|
|
4a20ae6cb4 | ||
|
|
a87b87825d | ||
|
|
976e3160b8 | ||
|
|
4890454935 | ||
|
|
ca887518dd | ||
|
|
5b5ae1ef52 | ||
|
|
3eec137100 | ||
|
|
402a3797a4 | ||
|
|
0b871505a6 | ||
|
|
0485ff1899 | ||
|
|
69df004b9f | ||
|
|
8b9ff893b3 | ||
|
|
b3d5383073 | ||
|
|
9d5f328a09 | ||
|
|
f469ee3010 | ||
|
|
e30ac91f16 | ||
|
|
95183c365a | ||
|
|
0af5e60094 | ||
|
|
3c030740ea | ||
|
|
bb9ce98379 | ||
|
|
02fda265b8 | ||
|
|
501919df35 | ||
|
|
107909de30 | ||
|
|
052b667d6a | ||
|
|
e5ea607853 | ||
|
|
223cc8314a | ||
|
|
39e6658d01 | ||
|
|
8f051c86f7 | ||
|
|
11419cbbfd | ||
|
|
5b7e8a27fb | ||
|
|
718e4b8665 | ||
|
|
d387d4df92 | ||
|
|
1fa845de6e | ||
|
|
7126f9dffc | ||
|
|
b2b4bd82d7 | ||
|
|
03c7bcf9c6 | ||
|
|
ea355b4262 | ||
|
|
71a0abe211 | ||
|
|
d8c3b8e7f8 | ||
|
|
88baff5693 | ||
|
|
6d3b129666 | ||
|
|
8295a3be0d | ||
|
|
17a273d237 | ||
|
|
752bb88445 | ||
|
|
a263f3978b | ||
|
|
0009e5cc27 | ||
|
|
96ea865681 | ||
|
|
4a7498a0be | ||
|
|
e3f0a53f59 | ||
|
|
3734c95fd4 | ||
|
|
9ba7bf96ef | ||
|
|
bff5ff0cb8 | ||
|
|
acb86066ff | ||
|
|
263bbf897a | ||
|
|
97124f5fce | ||
|
|
6565b0cfbe | ||
|
|
b635b5a7e3 | ||
|
|
6d74c83ddb | ||
|
|
7cf5767949 | ||
|
|
fa366f0099 | ||
|
|
989013c974 | ||
|
|
c09e999916 | ||
|
|
61b75c82ce | ||
|
|
773a4ae1f3 | ||
|
|
38ac84ad71 | ||
|
|
054ca6a8fb | ||
|
|
3d9f865247 | ||
|
|
5cd6fba11a | ||
|
|
f7b18d97a7 | ||
|
|
45e6e2adee | ||
|
|
704c1539a2 | ||
|
|
00b293d2e9 | ||
|
|
52e9dc66f5 | ||
|
|
3c8f7d380f | ||
|
|
0a0ddb0eaf | ||
|
|
84b8c9c2bc | ||
|
|
257e1be95f | ||
|
|
6003289717 | ||
|
|
19d285149b | ||
|
|
33a2e1bd50 | ||
|
|
b8507371d5 | ||
|
|
bddded96ff | ||
|
|
24024706bf | ||
|
|
93fd1c7075 | ||
|
|
5b1b2621ab | ||
|
|
b50c3852ef | ||
|
|
1924d0740d | ||
|
|
74459ec403 | ||
|
|
c32a421ea7 | ||
|
|
4cfe8fe588 | ||
|
|
06c82973c6 | ||
|
|
f638cd08ea | ||
|
|
31b87130a2 | ||
|
|
ca99a27611 | ||
|
|
5eb1fde88c | ||
|
|
24679af2e8 | ||
|
|
474b035d99 | ||
|
|
4f45c52674 | ||
|
|
f059be8e4d | ||
|
|
03435641c8 | ||
|
|
58be1d7759 | ||
|
|
4e0edaf544 | ||
|
|
9c74103fbe | ||
|
|
551c6f02a2 | ||
|
|
358cf48506 | ||
|
|
331d4ad849 | ||
|
|
bd32cecf7a | ||
|
|
c84f2e48b0 | ||
|
|
370b7f8e12 | ||
|
|
4cb649853d | ||
|
|
a774d1fa10 | ||
|
|
5254e74719 | ||
|
|
14b5e85461 | ||
|
|
25f1cdbcb5 | ||
|
|
34df1a030b | ||
|
|
1d729b2b0f | ||
|
|
f73a7a6ed8 | ||
|
|
de9bf327c6 | ||
|
|
5d19c26d59 | ||
|
|
0415d1ccef | ||
|
|
d10de5b7f9 | ||
|
|
5265b3eee7 | ||
|
|
9abe9e7e54 | ||
|
|
3a89e88033 | ||
|
|
2899b77cd5 | ||
|
|
41383e6fe4 | ||
|
|
64a13a5d42 | ||
|
|
43ea4af30f | ||
|
|
30f3be1da0 | ||
|
|
3e5cb8e04e | ||
|
|
c7b25a9fe4 | ||
|
|
fa38b741ce | ||
|
|
be233b49b6 | ||
|
|
35c0801d6c | ||
|
|
ee8bd9b3b5 | ||
|
|
30230aff73 | ||
|
|
d995f0e092 | ||
|
|
6e77d50563 | ||
|
|
3531dc85ae | ||
|
|
9c15f96e12 | ||
|
|
86a5e72a65 | ||
|
|
d5b63092ed | ||
|
|
6648b7d7da | ||
|
|
97c2c523a8 | ||
|
|
e682c230bd | ||
|
|
a3a4689af2 | ||
|
|
107412f2f2 | ||
|
|
6b2b484987 | ||
|
|
80877aa945 | ||
|
|
c52a2f2f7b |
@@ -39,6 +39,10 @@ jobs:
|
|||||||
vmImage: 'ubuntu-latest'
|
vmImage: 'ubuntu-latest'
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
- script: echo "##vso[task.setvariable variable=JellyfinVersion]$( awk -F '/' '{ print $NF }' <<<'$(Build.SourceBranch)' | sed 's/^v//' )"
|
||||||
|
displayName: Set release version (stable)
|
||||||
|
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
|
||||||
|
|
||||||
- script: 'docker build -f deployment/Dockerfile.$(BuildConfiguration) -t jellyfin-server-$(BuildConfiguration) deployment'
|
- script: 'docker build -f deployment/Dockerfile.$(BuildConfiguration) -t jellyfin-server-$(BuildConfiguration) deployment'
|
||||||
displayName: 'Build Dockerfile'
|
displayName: 'Build Dockerfile'
|
||||||
|
|
||||||
@@ -80,6 +84,10 @@ jobs:
|
|||||||
vmImage: 'ubuntu-latest'
|
vmImage: 'ubuntu-latest'
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
- script: echo "##vso[task.setvariable variable=JellyfinVersion]$( awk -F '/' '{ print $NF }' <<<'$(Build.SourceBranch)' | sed 's/^v//' )"
|
||||||
|
displayName: Set release version (stable)
|
||||||
|
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
|
||||||
|
|
||||||
- task: DownloadPipelineArtifact@2
|
- task: DownloadPipelineArtifact@2
|
||||||
displayName: 'Download OpenAPI Spec'
|
displayName: 'Download OpenAPI Spec'
|
||||||
inputs:
|
inputs:
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
../fedora/Makefile
|
|
||||||
29
.github/stale.yml
vendored
29
.github/stale.yml
vendored
@@ -1,29 +0,0 @@
|
|||||||
# Number of days of inactivity before an issue becomes stale
|
|
||||||
daysUntilStale: 120
|
|
||||||
# Number of days of inactivity before a stale issue is closed
|
|
||||||
daysUntilClose: 21
|
|
||||||
# Issues with these labels will never be considered stale
|
|
||||||
exemptLabels:
|
|
||||||
- regression
|
|
||||||
- security
|
|
||||||
- dotnet-3.0-future
|
|
||||||
- roadmap
|
|
||||||
- future
|
|
||||||
- feature
|
|
||||||
- enhancement
|
|
||||||
- confirmed
|
|
||||||
# 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: >
|
|
||||||
This issue has gone 120 days without comment. To avoid abandoned issues, it will be closed in 21 days if there are no new comments.
|
|
||||||
|
|
||||||
If you're the original submitter of this issue, please comment confirming if this issue still affects you in the latest release or nightlies, or close the issue if it has been fixed. If you're another user also affected by this bug, please comment confirming so. Either action will remove the stale label.
|
|
||||||
|
|
||||||
This bot exists to prevent issues from becoming stale and forgotten. Jellyfin is always moving forward, and bugs are often fixed as side effects of other changes. We therefore ask that bug report authors remain vigilant about their issues to ensure they are closed if fixed, or re-confirmed - perhaps with fresh logs or reproduction examples - regularly. 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
|
|
||||||
|
|
||||||
# Disable automatic closing of pull requests
|
|
||||||
pulls:
|
|
||||||
daysUntilClose: false
|
|
||||||
4
.github/workflows/codeql-analysis.yml
vendored
4
.github/workflows/codeql-analysis.yml
vendored
@@ -20,9 +20,9 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
- name: Setup .NET Core
|
- name: Setup .NET Core
|
||||||
uses: actions/setup-dotnet@v1
|
uses: actions/setup-dotnet@v2
|
||||||
with:
|
with:
|
||||||
dotnet-version: '6.0.x'
|
dotnet-version: '6.0.x'
|
||||||
|
|
||||||
|
|||||||
4
.github/workflows/commands.yml
vendored
4
.github/workflows/commands.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
|||||||
reactions: '+1'
|
reactions: '+1'
|
||||||
|
|
||||||
- name: Checkout the latest code
|
- name: Checkout the latest code
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.JF_BOT_TOKEN }}
|
token: ${{ secrets.JF_BOT_TOKEN }}
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
@@ -47,7 +47,7 @@ jobs:
|
|||||||
reactions: eyes
|
reactions: eyes
|
||||||
|
|
||||||
- name: Checkout the latest code
|
- name: Checkout the latest code
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.JF_BOT_TOKEN }}
|
token: ${{ secrets.JF_BOT_TOKEN }}
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|||||||
10
.github/workflows/openapi.yml
vendored
10
.github/workflows/openapi.yml
vendored
@@ -12,12 +12,12 @@ jobs:
|
|||||||
permissions: read-all
|
permissions: read-all
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.pull_request.head.ref }}
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
||||||
- name: Setup .NET Core
|
- name: Setup .NET Core
|
||||||
uses: actions/setup-dotnet@v1
|
uses: actions/setup-dotnet@v2
|
||||||
with:
|
with:
|
||||||
dotnet-version: '6.0.x'
|
dotnet-version: '6.0.x'
|
||||||
- name: Generate openapi.json
|
- name: Generate openapi.json
|
||||||
@@ -37,11 +37,11 @@ jobs:
|
|||||||
permissions: read-all
|
permissions: read-all
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.base_ref }}
|
ref: ${{ github.base_ref }}
|
||||||
- name: Setup .NET Core
|
- name: Setup .NET Core
|
||||||
uses: actions/setup-dotnet@v1
|
uses: actions/setup-dotnet@v2
|
||||||
with:
|
with:
|
||||||
dotnet-version: '6.0.x'
|
dotnet-version: '6.0.x'
|
||||||
- name: Generate openapi.json
|
- name: Generate openapi.json
|
||||||
|
|||||||
27
.github/workflows/repo-stale.yaml
vendored
Normal file
27
.github/workflows/repo-stale.yaml
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
name: Issue Stale Check
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '30 1 * * *'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
stale:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: ${{ contains(github.repository, 'jellyfin/') }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/stale@v5
|
||||||
|
with:
|
||||||
|
repo-token: ${{ secrets.JF_BOT_TOKEN }}
|
||||||
|
days-before-stale: 120
|
||||||
|
days-before-pr-stale: -1
|
||||||
|
days-before-close: 21
|
||||||
|
days-before-pr-close: -1
|
||||||
|
exempt-issue-labels: regression,security,roadmap,future,feature,enhancement,confirmed
|
||||||
|
stale-issue-label: stale
|
||||||
|
stale-issue-message: |-
|
||||||
|
This issue has gone 120 days without comment. To avoid abandoned issues, it will be closed in 21 days if there are no new comments.
|
||||||
|
|
||||||
|
If you're the original submitter of this issue, please comment confirming if this issue still affects you in the latest release or master branch, or close the issue if it has been fixed. If you're another user also affected by this bug, please comment confirming so. Either action will remove the stale label.
|
||||||
|
|
||||||
|
This bot exists to prevent issues from becoming stale and forgotten. Jellyfin is always moving forward, and bugs are often fixed as side effects of other changes. We therefore ask that bug report authors remain vigilant about their issues to ensure they are closed if fixed, or re-confirmed - perhaps with fresh logs or reproduction examples - regularly. If you have any questions you can reach us on [Matrix or Social Media](https://docs.jellyfin.org/general/getting-help.html).
|
||||||
4
BannedSymbols.txt
Normal file
4
BannedSymbols.txt
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
P:System.Threading.Tasks.Task`1.Result
|
||||||
|
M:System.Guid.op_Equality(System.Guid,System.Guid)
|
||||||
|
M:System.Guid.op_Inequality(System.Guid,System.Guid)
|
||||||
|
M:System.Guid.Equals(System.Object)
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
# Jellyfin Contributors
|
# Jellyfin Contributors
|
||||||
|
|
||||||
|
- [1337joe](https://github.com/1337joe)
|
||||||
- [97carmine](https://github.com/97carmine)
|
- [97carmine](https://github.com/97carmine)
|
||||||
- [Abbe98](https://github.com/Abbe98)
|
- [Abbe98](https://github.com/Abbe98)
|
||||||
- [agrenott](https://github.com/agrenott)
|
- [agrenott](https://github.com/agrenott)
|
||||||
@@ -45,6 +46,7 @@
|
|||||||
- [Froghut](https://github.com/Froghut)
|
- [Froghut](https://github.com/Froghut)
|
||||||
- [fruhnow](https://github.com/fruhnow)
|
- [fruhnow](https://github.com/fruhnow)
|
||||||
- [geilername](https://github.com/geilername)
|
- [geilername](https://github.com/geilername)
|
||||||
|
- [GermanCoding](https://github.com/GermanCoding)
|
||||||
- [gnattu](https://github.com/gnattu)
|
- [gnattu](https://github.com/gnattu)
|
||||||
- [GodTamIt](https://github.com/GodTamIt)
|
- [GodTamIt](https://github.com/GodTamIt)
|
||||||
- [grafixeyehero](https://github.com/grafixeyehero)
|
- [grafixeyehero](https://github.com/grafixeyehero)
|
||||||
@@ -76,6 +78,7 @@
|
|||||||
- [mitchfizz05](https://github.com/mitchfizz05)
|
- [mitchfizz05](https://github.com/mitchfizz05)
|
||||||
- [MrTimscampi](https://github.com/MrTimscampi)
|
- [MrTimscampi](https://github.com/MrTimscampi)
|
||||||
- [n8225](https://github.com/n8225)
|
- [n8225](https://github.com/n8225)
|
||||||
|
- [Nalsai](https://github.com/Nalsai)
|
||||||
- [Narfinger](https://github.com/Narfinger)
|
- [Narfinger](https://github.com/Narfinger)
|
||||||
- [NathanPickard](https://github.com/NathanPickard)
|
- [NathanPickard](https://github.com/NathanPickard)
|
||||||
- [neilsb](https://github.com/neilsb)
|
- [neilsb](https://github.com/neilsb)
|
||||||
@@ -115,6 +118,7 @@
|
|||||||
- [ssenart](https://github.com/ssenart)
|
- [ssenart](https://github.com/ssenart)
|
||||||
- [stanionascu](https://github.com/stanionascu)
|
- [stanionascu](https://github.com/stanionascu)
|
||||||
- [stevehayles](https://github.com/stevehayles)
|
- [stevehayles](https://github.com/stevehayles)
|
||||||
|
- [StollD](https://github.com/StollD)
|
||||||
- [SuperSandro2000](https://github.com/SuperSandro2000)
|
- [SuperSandro2000](https://github.com/SuperSandro2000)
|
||||||
- [tbraeutigam](https://github.com/tbraeutigam)
|
- [tbraeutigam](https://github.com/tbraeutigam)
|
||||||
- [teacupx](https://github.com/teacupx)
|
- [teacupx](https://github.com/teacupx)
|
||||||
@@ -150,6 +154,9 @@
|
|||||||
- [ianjazz246](https://github.com/ianjazz246)
|
- [ianjazz246](https://github.com/ianjazz246)
|
||||||
- [peterspenler](https://github.com/peterspenler)
|
- [peterspenler](https://github.com/peterspenler)
|
||||||
- [MBR-0001](https://github.com/MBR-0001)
|
- [MBR-0001](https://github.com/MBR-0001)
|
||||||
|
- [jonas-resch](https://github.com/jonas-resch)
|
||||||
|
- [vgambier](https://github.com/vgambier)
|
||||||
|
- [MinecraftPlaye](https://github.com/MinecraftPlaye)
|
||||||
|
|
||||||
# Emby Contributors
|
# Emby Contributors
|
||||||
|
|
||||||
@@ -216,3 +223,5 @@
|
|||||||
- [olsh](https://github.com/olsh)
|
- [olsh](https://github.com/olsh)
|
||||||
- [lbenini](https://github.com/lbenini)
|
- [lbenini](https://github.com/lbenini)
|
||||||
- [gnuyent](https://github.com/gnuyent)
|
- [gnuyent](https://github.com/gnuyent)
|
||||||
|
- [Matthew Jones](https://github.com/matthew-jones-uk)
|
||||||
|
- [Jakob Kukla](https://github.com/jakobkukla)
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)/jellyfin.ruleset</CodeAnalysisRuleSet>
|
<CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)/jellyfin.ruleset</CodeAnalysisRuleSet>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
<PropertyGroup>
|
||||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
@@ -14,4 +14,8 @@
|
|||||||
<AnalysisMode>AllEnabledByDefault</AnalysisMode>
|
<AnalysisMode>AllEnabledByDefault</AnalysisMode>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<AdditionalFiles Include="$(SolutionDir)/BannedSymbols.txt" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
13
Dockerfile
13
Dockerfile
@@ -22,10 +22,10 @@ ARG APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=DontWarn
|
|||||||
ENV NVIDIA_DRIVER_CAPABILITIES="compute,video,utility"
|
ENV NVIDIA_DRIVER_CAPABILITIES="compute,video,utility"
|
||||||
|
|
||||||
# https://github.com/intel/compute-runtime/releases
|
# https://github.com/intel/compute-runtime/releases
|
||||||
ARG GMMLIB_VERSION=21.2.1
|
ARG GMMLIB_VERSION=22.0.2
|
||||||
ARG IGC_VERSION=1.0.8517
|
ARG IGC_VERSION=1.0.10395
|
||||||
ARG NEO_VERSION=21.35.20826
|
ARG NEO_VERSION=22.08.22549
|
||||||
ARG LEVEL_ZERO_VERSION=1.2.20826
|
ARG LEVEL_ZERO_VERSION=1.3.22549
|
||||||
|
|
||||||
# Install dependencies:
|
# Install dependencies:
|
||||||
# mesa-va-drivers: needed for AMD VAAPI. Mesa >= 20.1 is required for HEVC transcoding.
|
# mesa-va-drivers: needed for AMD VAAPI. Mesa >= 20.1 is required for HEVC transcoding.
|
||||||
@@ -48,8 +48,7 @@ RUN apt-get update \
|
|||||||
&& wget https://github.com/intel/compute-runtime/releases/download/${NEO_VERSION}/intel-gmmlib_${GMMLIB_VERSION}_amd64.deb \
|
&& wget https://github.com/intel/compute-runtime/releases/download/${NEO_VERSION}/intel-gmmlib_${GMMLIB_VERSION}_amd64.deb \
|
||||||
&& wget https://github.com/intel/intel-graphics-compiler/releases/download/igc-${IGC_VERSION}/intel-igc-core_${IGC_VERSION}_amd64.deb \
|
&& wget https://github.com/intel/intel-graphics-compiler/releases/download/igc-${IGC_VERSION}/intel-igc-core_${IGC_VERSION}_amd64.deb \
|
||||||
&& wget https://github.com/intel/intel-graphics-compiler/releases/download/igc-${IGC_VERSION}/intel-igc-opencl_${IGC_VERSION}_amd64.deb \
|
&& wget https://github.com/intel/intel-graphics-compiler/releases/download/igc-${IGC_VERSION}/intel-igc-opencl_${IGC_VERSION}_amd64.deb \
|
||||||
&& wget https://github.com/intel/compute-runtime/releases/download/${NEO_VERSION}/intel-opencl_${NEO_VERSION}_amd64.deb \
|
&& wget https://github.com/intel/compute-runtime/releases/download/${NEO_VERSION}/intel-opencl-icd_${NEO_VERSION}_amd64.deb \
|
||||||
&& wget https://github.com/intel/compute-runtime/releases/download/${NEO_VERSION}/intel-ocloc_${NEO_VERSION}_amd64.deb \
|
|
||||||
&& wget https://github.com/intel/compute-runtime/releases/download/${NEO_VERSION}/intel-level-zero-gpu_${LEVEL_ZERO_VERSION}_amd64.deb \
|
&& wget https://github.com/intel/compute-runtime/releases/download/${NEO_VERSION}/intel-level-zero-gpu_${LEVEL_ZERO_VERSION}_amd64.deb \
|
||||||
&& dpkg -i *.deb \
|
&& dpkg -i *.deb \
|
||||||
&& cd .. \
|
&& cd .. \
|
||||||
@@ -83,7 +82,7 @@ COPY --from=builder /jellyfin /jellyfin
|
|||||||
COPY --from=web-builder /dist /jellyfin/jellyfin-web
|
COPY --from=web-builder /dist /jellyfin/jellyfin-web
|
||||||
|
|
||||||
EXPOSE 8096
|
EXPOSE 8096
|
||||||
VOLUME /cache /config /media
|
VOLUME /cache /config
|
||||||
ENTRYPOINT ["./jellyfin/jellyfin", \
|
ENTRYPOINT ["./jellyfin/jellyfin", \
|
||||||
"--datadir", "/config", \
|
"--datadir", "/config", \
|
||||||
"--cachedir", "/cache", \
|
"--cachedir", "/cache", \
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ COPY --from=builder /jellyfin /jellyfin
|
|||||||
COPY --from=web-builder /dist /jellyfin/jellyfin-web
|
COPY --from=web-builder /dist /jellyfin/jellyfin-web
|
||||||
|
|
||||||
EXPOSE 8096
|
EXPOSE 8096
|
||||||
VOLUME /cache /config /media
|
VOLUME /cache /config
|
||||||
ENTRYPOINT ["./jellyfin/jellyfin", \
|
ENTRYPOINT ["./jellyfin/jellyfin", \
|
||||||
"--datadir", "/config", \
|
"--datadir", "/config", \
|
||||||
"--cachedir", "/cache", \
|
"--cachedir", "/cache", \
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ COPY --from=builder /jellyfin /jellyfin
|
|||||||
COPY --from=web-builder /dist /jellyfin/jellyfin-web
|
COPY --from=web-builder /dist /jellyfin/jellyfin-web
|
||||||
|
|
||||||
EXPOSE 8096
|
EXPOSE 8096
|
||||||
VOLUME /cache /config /media
|
VOLUME /cache /config
|
||||||
ENTRYPOINT ["./jellyfin/jellyfin", \
|
ENTRYPOINT ["./jellyfin/jellyfin", \
|
||||||
"--datadir", "/config", \
|
"--datadir", "/config", \
|
||||||
"--cachedir", "/cache", \
|
"--cachedir", "/cache", \
|
||||||
|
|||||||
@@ -18,11 +18,8 @@ using MediaBrowser.Controller.Drawing;
|
|||||||
using MediaBrowser.Controller.Dto;
|
using MediaBrowser.Controller.Dto;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Entities.Audio;
|
using MediaBrowser.Controller.Entities.Audio;
|
||||||
using MediaBrowser.Controller.Entities.Movies;
|
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.LiveTv;
|
|
||||||
using MediaBrowser.Controller.MediaEncoding;
|
using MediaBrowser.Controller.MediaEncoding;
|
||||||
using MediaBrowser.Controller.Playlists;
|
|
||||||
using MediaBrowser.Controller.TV;
|
using MediaBrowser.Controller.TV;
|
||||||
using MediaBrowser.Model.Dlna;
|
using MediaBrowser.Model.Dlna;
|
||||||
using MediaBrowser.Model.Dto;
|
using MediaBrowser.Model.Dto;
|
||||||
@@ -30,12 +27,7 @@ using MediaBrowser.Model.Entities;
|
|||||||
using MediaBrowser.Model.Globalization;
|
using MediaBrowser.Model.Globalization;
|
||||||
using MediaBrowser.Model.Querying;
|
using MediaBrowser.Model.Querying;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Book = MediaBrowser.Controller.Entities.Book;
|
|
||||||
using Episode = MediaBrowser.Controller.Entities.TV.Episode;
|
|
||||||
using Genre = MediaBrowser.Controller.Entities.Genre;
|
using Genre = MediaBrowser.Controller.Entities.Genre;
|
||||||
using Movie = MediaBrowser.Controller.Entities.Movies.Movie;
|
|
||||||
using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum;
|
|
||||||
using Series = MediaBrowser.Controller.Entities.TV.Series;
|
|
||||||
|
|
||||||
namespace Emby.Dlna.ContentDirectory
|
namespace Emby.Dlna.ContentDirectory
|
||||||
{
|
{
|
||||||
@@ -395,6 +387,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
}
|
}
|
||||||
|
|
||||||
writer.WriteFullEndElement();
|
writer.WriteFullEndElement();
|
||||||
|
writer.Flush();
|
||||||
xmlWriter.WriteElementString("Result", builder.ToString());
|
xmlWriter.WriteElementString("Result", builder.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -484,6 +477,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
}
|
}
|
||||||
|
|
||||||
writer.WriteFullEndElement();
|
writer.WriteFullEndElement();
|
||||||
|
writer.Flush();
|
||||||
xmlWriter.WriteElementString("Result", builder.ToString());
|
xmlWriter.WriteElementString("Result", builder.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -537,7 +531,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
User = user,
|
User = user,
|
||||||
Recursive = true,
|
Recursive = true,
|
||||||
IsMissing = false,
|
IsMissing = false,
|
||||||
ExcludeItemTypes = new[] { nameof(Book) },
|
ExcludeItemTypes = new[] { BaseItemKind.Book },
|
||||||
IsFolder = isFolder,
|
IsFolder = isFolder,
|
||||||
MediaTypes = mediaTypes,
|
MediaTypes = mediaTypes,
|
||||||
DtoOptions = GetDtoOptions()
|
DtoOptions = GetDtoOptions()
|
||||||
@@ -617,7 +611,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
Limit = limit,
|
Limit = limit,
|
||||||
StartIndex = startIndex,
|
StartIndex = startIndex,
|
||||||
IsVirtualItem = false,
|
IsVirtualItem = false,
|
||||||
ExcludeItemTypes = new[] { nameof(Book) },
|
ExcludeItemTypes = new[] { BaseItemKind.Book },
|
||||||
IsPlaceHolder = false,
|
IsPlaceHolder = false,
|
||||||
DtoOptions = GetDtoOptions(),
|
DtoOptions = GetDtoOptions(),
|
||||||
OrderBy = GetOrderBy(sort, folder.IsPreSorted)
|
OrderBy = GetOrderBy(sort, folder.IsPreSorted)
|
||||||
@@ -625,7 +619,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
|
|
||||||
var queryResult = folder.GetItems(query);
|
var queryResult = folder.GetItems(query);
|
||||||
|
|
||||||
return ToResult(queryResult);
|
return ToResult(startIndex, queryResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -642,13 +636,13 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
{
|
{
|
||||||
StartIndex = startIndex,
|
StartIndex = startIndex,
|
||||||
Limit = limit,
|
Limit = limit,
|
||||||
IncludeItemTypes = new[] { nameof(LiveTvChannel) },
|
IncludeItemTypes = new[] { BaseItemKind.LiveTvChannel },
|
||||||
OrderBy = GetOrderBy(sort, false)
|
OrderBy = GetOrderBy(sort, false)
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = _libraryManager.GetItemsResult(query);
|
var result = _libraryManager.GetItemsResult(query);
|
||||||
|
|
||||||
return ToResult(result);
|
return ToResult(startIndex, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -673,39 +667,39 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
switch (stubType)
|
switch (stubType)
|
||||||
{
|
{
|
||||||
case StubType.Latest:
|
case StubType.Latest:
|
||||||
return GetLatest(item, query, nameof(Audio));
|
return GetLatest(item, query, BaseItemKind.Audio);
|
||||||
case StubType.Playlists:
|
case StubType.Playlists:
|
||||||
return GetMusicPlaylists(query);
|
return GetMusicPlaylists(query);
|
||||||
case StubType.Albums:
|
case StubType.Albums:
|
||||||
return GetChildrenOfItem(item, query, nameof(MusicAlbum));
|
return GetChildrenOfItem(item, query, BaseItemKind.MusicAlbum);
|
||||||
case StubType.Artists:
|
case StubType.Artists:
|
||||||
return GetMusicArtists(item, query);
|
return GetMusicArtists(item, query);
|
||||||
case StubType.AlbumArtists:
|
case StubType.AlbumArtists:
|
||||||
return GetMusicAlbumArtists(item, query);
|
return GetMusicAlbumArtists(item, query);
|
||||||
case StubType.FavoriteAlbums:
|
case StubType.FavoriteAlbums:
|
||||||
return GetChildrenOfItem(item, query, nameof(MusicAlbum), true);
|
return GetChildrenOfItem(item, query, BaseItemKind.MusicAlbum, true);
|
||||||
case StubType.FavoriteArtists:
|
case StubType.FavoriteArtists:
|
||||||
return GetFavoriteArtists(item, query);
|
return GetFavoriteArtists(item, query);
|
||||||
case StubType.FavoriteSongs:
|
case StubType.FavoriteSongs:
|
||||||
return GetChildrenOfItem(item, query, nameof(Audio), true);
|
return GetChildrenOfItem(item, query, BaseItemKind.Audio, true);
|
||||||
case StubType.Songs:
|
case StubType.Songs:
|
||||||
return GetChildrenOfItem(item, query, nameof(Audio));
|
return GetChildrenOfItem(item, query, BaseItemKind.Audio);
|
||||||
case StubType.Genres:
|
case StubType.Genres:
|
||||||
return GetMusicGenres(item, query);
|
return GetMusicGenres(item, query);
|
||||||
}
|
}
|
||||||
|
|
||||||
var serverItems = new ServerItem[]
|
var serverItems = new ServerItem[]
|
||||||
{
|
{
|
||||||
new (item, StubType.Latest),
|
new(item, StubType.Latest),
|
||||||
new (item, StubType.Playlists),
|
new(item, StubType.Playlists),
|
||||||
new (item, StubType.Albums),
|
new(item, StubType.Albums),
|
||||||
new (item, StubType.AlbumArtists),
|
new(item, StubType.AlbumArtists),
|
||||||
new (item, StubType.Artists),
|
new(item, StubType.Artists),
|
||||||
new (item, StubType.Songs),
|
new(item, StubType.Songs),
|
||||||
new (item, StubType.Genres),
|
new(item, StubType.Genres),
|
||||||
new (item, StubType.FavoriteArtists),
|
new(item, StubType.FavoriteArtists),
|
||||||
new (item, StubType.FavoriteAlbums),
|
new(item, StubType.FavoriteAlbums),
|
||||||
new (item, StubType.FavoriteSongs)
|
new(item, StubType.FavoriteSongs)
|
||||||
};
|
};
|
||||||
|
|
||||||
if (limit < serverItems.Length)
|
if (limit < serverItems.Length)
|
||||||
@@ -713,11 +707,10 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
serverItems = serverItems[..limit.Value];
|
serverItems = serverItems[..limit.Value];
|
||||||
}
|
}
|
||||||
|
|
||||||
return new QueryResult<ServerItem>
|
return new QueryResult<ServerItem>(
|
||||||
{
|
startIndex,
|
||||||
Items = serverItems,
|
serverItems.Length,
|
||||||
TotalRecordCount = serverItems.Length
|
serverItems);
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -744,25 +737,25 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
case StubType.ContinueWatching:
|
case StubType.ContinueWatching:
|
||||||
return GetMovieContinueWatching(item, query);
|
return GetMovieContinueWatching(item, query);
|
||||||
case StubType.Latest:
|
case StubType.Latest:
|
||||||
return GetLatest(item, query, nameof(Movie));
|
return GetLatest(item, query, BaseItemKind.Movie);
|
||||||
case StubType.Movies:
|
case StubType.Movies:
|
||||||
return GetChildrenOfItem(item, query, nameof(Movie));
|
return GetChildrenOfItem(item, query, BaseItemKind.Movie);
|
||||||
case StubType.Collections:
|
case StubType.Collections:
|
||||||
return GetMovieCollections(query);
|
return GetMovieCollections(query);
|
||||||
case StubType.Favorites:
|
case StubType.Favorites:
|
||||||
return GetChildrenOfItem(item, query, nameof(Movie), true);
|
return GetChildrenOfItem(item, query, BaseItemKind.Movie, true);
|
||||||
case StubType.Genres:
|
case StubType.Genres:
|
||||||
return GetGenres(item, query);
|
return GetGenres(item, query);
|
||||||
}
|
}
|
||||||
|
|
||||||
var array = new ServerItem[]
|
var array = new ServerItem[]
|
||||||
{
|
{
|
||||||
new (item, StubType.ContinueWatching),
|
new(item, StubType.ContinueWatching),
|
||||||
new (item, StubType.Latest),
|
new(item, StubType.Latest),
|
||||||
new (item, StubType.Movies),
|
new(item, StubType.Movies),
|
||||||
new (item, StubType.Collections),
|
new(item, StubType.Collections),
|
||||||
new (item, StubType.Favorites),
|
new(item, StubType.Favorites),
|
||||||
new (item, StubType.Genres)
|
new(item, StubType.Genres)
|
||||||
};
|
};
|
||||||
|
|
||||||
if (limit < array.Length)
|
if (limit < array.Length)
|
||||||
@@ -770,11 +763,10 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
array = array[..limit.Value];
|
array = array[..limit.Value];
|
||||||
}
|
}
|
||||||
|
|
||||||
return new QueryResult<ServerItem>
|
return new QueryResult<ServerItem>(
|
||||||
{
|
startIndex,
|
||||||
Items = array,
|
array.Length,
|
||||||
TotalRecordCount = array.Length
|
array);
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -796,11 +788,10 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
.Select(i => new ServerItem(i, StubType.Folder))
|
.Select(i => new ServerItem(i, StubType.Folder))
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
return new QueryResult<ServerItem>
|
return new QueryResult<ServerItem>(
|
||||||
{
|
startIndex,
|
||||||
Items = items,
|
totalRecordCount,
|
||||||
TotalRecordCount = totalRecordCount
|
items);
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -829,26 +820,26 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
case StubType.NextUp:
|
case StubType.NextUp:
|
||||||
return GetNextUp(item, query);
|
return GetNextUp(item, query);
|
||||||
case StubType.Latest:
|
case StubType.Latest:
|
||||||
return GetLatest(item, query, nameof(Episode));
|
return GetLatest(item, query, BaseItemKind.Episode);
|
||||||
case StubType.Series:
|
case StubType.Series:
|
||||||
return GetChildrenOfItem(item, query, nameof(Series));
|
return GetChildrenOfItem(item, query, BaseItemKind.Series);
|
||||||
case StubType.FavoriteSeries:
|
case StubType.FavoriteSeries:
|
||||||
return GetChildrenOfItem(item, query, nameof(Series), true);
|
return GetChildrenOfItem(item, query, BaseItemKind.Series, true);
|
||||||
case StubType.FavoriteEpisodes:
|
case StubType.FavoriteEpisodes:
|
||||||
return GetChildrenOfItem(item, query, nameof(Episode), true);
|
return GetChildrenOfItem(item, query, BaseItemKind.Episode, true);
|
||||||
case StubType.Genres:
|
case StubType.Genres:
|
||||||
return GetGenres(item, query);
|
return GetGenres(item, query);
|
||||||
}
|
}
|
||||||
|
|
||||||
var serverItems = new ServerItem[]
|
var serverItems = new ServerItem[]
|
||||||
{
|
{
|
||||||
new (item, StubType.ContinueWatching),
|
new(item, StubType.ContinueWatching),
|
||||||
new (item, StubType.NextUp),
|
new(item, StubType.NextUp),
|
||||||
new (item, StubType.Latest),
|
new(item, StubType.Latest),
|
||||||
new (item, StubType.Series),
|
new(item, StubType.Series),
|
||||||
new (item, StubType.FavoriteSeries),
|
new(item, StubType.FavoriteSeries),
|
||||||
new (item, StubType.FavoriteEpisodes),
|
new(item, StubType.FavoriteEpisodes),
|
||||||
new (item, StubType.Genres)
|
new(item, StubType.Genres)
|
||||||
};
|
};
|
||||||
|
|
||||||
if (limit < serverItems.Length)
|
if (limit < serverItems.Length)
|
||||||
@@ -856,11 +847,10 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
serverItems = serverItems[..limit.Value];
|
serverItems = serverItems[..limit.Value];
|
||||||
}
|
}
|
||||||
|
|
||||||
return new QueryResult<ServerItem>
|
return new QueryResult<ServerItem>(
|
||||||
{
|
startIndex,
|
||||||
Items = serverItems,
|
serverItems.Length,
|
||||||
TotalRecordCount = serverItems.Length
|
serverItems);
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -885,7 +875,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
|
|
||||||
var result = _libraryManager.GetItemsResult(query);
|
var result = _libraryManager.GetItemsResult(query);
|
||||||
|
|
||||||
return ToResult(result);
|
return ToResult(query.StartIndex, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -896,11 +886,11 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
private QueryResult<ServerItem> GetMovieCollections(InternalItemsQuery query)
|
private QueryResult<ServerItem> GetMovieCollections(InternalItemsQuery query)
|
||||||
{
|
{
|
||||||
query.Recursive = true;
|
query.Recursive = true;
|
||||||
query.IncludeItemTypes = new[] { nameof(BoxSet) };
|
query.IncludeItemTypes = new[] { BaseItemKind.BoxSet };
|
||||||
|
|
||||||
var result = _libraryManager.GetItemsResult(query);
|
var result = _libraryManager.GetItemsResult(query);
|
||||||
|
|
||||||
return ToResult(result);
|
return ToResult(query.StartIndex, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -911,7 +901,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
/// <param name="itemType">The item type.</param>
|
/// <param name="itemType">The item type.</param>
|
||||||
/// <param name="isFavorite">A value indicating whether to only fetch favorite items.</param>
|
/// <param name="isFavorite">A value indicating whether to only fetch favorite items.</param>
|
||||||
/// <returns>The <see cref="QueryResult{ServerItem}"/>.</returns>
|
/// <returns>The <see cref="QueryResult{ServerItem}"/>.</returns>
|
||||||
private QueryResult<ServerItem> GetChildrenOfItem(BaseItem parent, InternalItemsQuery query, string itemType, bool isFavorite = false)
|
private QueryResult<ServerItem> GetChildrenOfItem(BaseItem parent, InternalItemsQuery query, BaseItemKind itemType, bool isFavorite = false)
|
||||||
{
|
{
|
||||||
query.Recursive = true;
|
query.Recursive = true;
|
||||||
query.Parent = parent;
|
query.Parent = parent;
|
||||||
@@ -920,7 +910,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
|
|
||||||
var result = _libraryManager.GetItemsResult(query);
|
var result = _libraryManager.GetItemsResult(query);
|
||||||
|
|
||||||
return ToResult(result);
|
return ToResult(query.StartIndex, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -937,7 +927,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
query.AncestorIds = new[] { parent.Id };
|
query.AncestorIds = new[] { parent.Id };
|
||||||
var genresResult = _libraryManager.GetGenres(query);
|
var genresResult = _libraryManager.GetGenres(query);
|
||||||
|
|
||||||
return ToResult(genresResult);
|
return ToResult(query.StartIndex, genresResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -953,7 +943,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
query.AncestorIds = new[] { parent.Id };
|
query.AncestorIds = new[] { parent.Id };
|
||||||
var genresResult = _libraryManager.GetMusicGenres(query);
|
var genresResult = _libraryManager.GetMusicGenres(query);
|
||||||
|
|
||||||
return ToResult(genresResult);
|
return ToResult(query.StartIndex, genresResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -969,7 +959,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
query.AncestorIds = new[] { parent.Id };
|
query.AncestorIds = new[] { parent.Id };
|
||||||
var artists = _libraryManager.GetAlbumArtists(query);
|
var artists = _libraryManager.GetAlbumArtists(query);
|
||||||
|
|
||||||
return ToResult(artists);
|
return ToResult(query.StartIndex, artists);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -984,7 +974,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
query.OrderBy = Array.Empty<(string, SortOrder)>();
|
query.OrderBy = Array.Empty<(string, SortOrder)>();
|
||||||
query.AncestorIds = new[] { parent.Id };
|
query.AncestorIds = new[] { parent.Id };
|
||||||
var artists = _libraryManager.GetArtists(query);
|
var artists = _libraryManager.GetArtists(query);
|
||||||
return ToResult(artists);
|
return ToResult(query.StartIndex, artists);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -1000,7 +990,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
query.AncestorIds = new[] { parent.Id };
|
query.AncestorIds = new[] { parent.Id };
|
||||||
query.IsFavorite = true;
|
query.IsFavorite = true;
|
||||||
var artists = _libraryManager.GetArtists(query);
|
var artists = _libraryManager.GetArtists(query);
|
||||||
return ToResult(artists);
|
return ToResult(query.StartIndex, artists);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -1011,12 +1001,12 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
private QueryResult<ServerItem> GetMusicPlaylists(InternalItemsQuery query)
|
private QueryResult<ServerItem> GetMusicPlaylists(InternalItemsQuery query)
|
||||||
{
|
{
|
||||||
query.Parent = null;
|
query.Parent = null;
|
||||||
query.IncludeItemTypes = new[] { nameof(Playlist) };
|
query.IncludeItemTypes = new[] { BaseItemKind.Playlist };
|
||||||
query.Recursive = true;
|
query.Recursive = true;
|
||||||
|
|
||||||
var result = _libraryManager.GetItemsResult(query);
|
var result = _libraryManager.GetItemsResult(query);
|
||||||
|
|
||||||
return ToResult(result);
|
return ToResult(query.StartIndex, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -1040,7 +1030,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
new[] { parent },
|
new[] { parent },
|
||||||
query.DtoOptions);
|
query.DtoOptions);
|
||||||
|
|
||||||
return ToResult(result);
|
return ToResult(query.StartIndex, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -1050,7 +1040,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
/// <param name="query">The <see cref="InternalItemsQuery"/>.</param>
|
/// <param name="query">The <see cref="InternalItemsQuery"/>.</param>
|
||||||
/// <param name="itemType">The item type.</param>
|
/// <param name="itemType">The item type.</param>
|
||||||
/// <returns>The <see cref="QueryResult{ServerItem}"/>.</returns>
|
/// <returns>The <see cref="QueryResult{ServerItem}"/>.</returns>
|
||||||
private QueryResult<ServerItem> GetLatest(BaseItem parent, InternalItemsQuery query, string itemType)
|
private QueryResult<ServerItem> GetLatest(BaseItem parent, InternalItemsQuery query, BaseItemKind itemType)
|
||||||
{
|
{
|
||||||
query.OrderBy = Array.Empty<(string, SortOrder)>();
|
query.OrderBy = Array.Empty<(string, SortOrder)>();
|
||||||
|
|
||||||
@@ -1066,7 +1056,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
},
|
},
|
||||||
query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).Where(i => i != null).ToArray();
|
query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).Where(i => i != null).ToArray();
|
||||||
|
|
||||||
return ToResult(items);
|
return ToResult(query.StartIndex, items);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -1084,7 +1074,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
{
|
{
|
||||||
Recursive = true,
|
Recursive = true,
|
||||||
ArtistIds = new[] { item.Id },
|
ArtistIds = new[] { item.Id },
|
||||||
IncludeItemTypes = new[] { nameof(MusicAlbum) },
|
IncludeItemTypes = new[] { BaseItemKind.MusicAlbum },
|
||||||
Limit = limit,
|
Limit = limit,
|
||||||
StartIndex = startIndex,
|
StartIndex = startIndex,
|
||||||
DtoOptions = GetDtoOptions(),
|
DtoOptions = GetDtoOptions(),
|
||||||
@@ -1093,7 +1083,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
|
|
||||||
var result = _libraryManager.GetItemsResult(query);
|
var result = _libraryManager.GetItemsResult(query);
|
||||||
|
|
||||||
return ToResult(result);
|
return ToResult(startIndex, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -1113,8 +1103,8 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
GenreIds = new[] { item.Id },
|
GenreIds = new[] { item.Id },
|
||||||
IncludeItemTypes = new[]
|
IncludeItemTypes = new[]
|
||||||
{
|
{
|
||||||
nameof(Movie),
|
BaseItemKind.Movie,
|
||||||
nameof(Series)
|
BaseItemKind.Series
|
||||||
},
|
},
|
||||||
Limit = limit,
|
Limit = limit,
|
||||||
StartIndex = startIndex,
|
StartIndex = startIndex,
|
||||||
@@ -1124,7 +1114,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
|
|
||||||
var result = _libraryManager.GetItemsResult(query);
|
var result = _libraryManager.GetItemsResult(query);
|
||||||
|
|
||||||
return ToResult(result);
|
return ToResult(startIndex, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -1142,7 +1132,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
{
|
{
|
||||||
Recursive = true,
|
Recursive = true,
|
||||||
GenreIds = new[] { item.Id },
|
GenreIds = new[] { item.Id },
|
||||||
IncludeItemTypes = new[] { nameof(MusicAlbum) },
|
IncludeItemTypes = new[] { BaseItemKind.MusicAlbum },
|
||||||
Limit = limit,
|
Limit = limit,
|
||||||
StartIndex = startIndex,
|
StartIndex = startIndex,
|
||||||
DtoOptions = GetDtoOptions(),
|
DtoOptions = GetDtoOptions(),
|
||||||
@@ -1151,33 +1141,34 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
|
|
||||||
var result = _libraryManager.GetItemsResult(query);
|
var result = _libraryManager.GetItemsResult(query);
|
||||||
|
|
||||||
return ToResult(result);
|
return ToResult(startIndex, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Converts <see cref="IReadOnlyCollection{BaseItem}"/> into a <see cref="QueryResult{ServerItem}"/>.
|
/// Converts <see cref="IReadOnlyCollection{BaseItem}"/> into a <see cref="QueryResult{ServerItem}"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="startIndex">The start index.</param>
|
||||||
/// <param name="result">An array of <see cref="BaseItem"/>.</param>
|
/// <param name="result">An array of <see cref="BaseItem"/>.</param>
|
||||||
/// <returns>A <see cref="QueryResult{ServerItem}"/>.</returns>
|
/// <returns>A <see cref="QueryResult{ServerItem}"/>.</returns>
|
||||||
private static QueryResult<ServerItem> ToResult(IReadOnlyCollection<BaseItem> result)
|
private static QueryResult<ServerItem> ToResult(int? startIndex, IReadOnlyCollection<BaseItem> result)
|
||||||
{
|
{
|
||||||
var serverItems = result
|
var serverItems = result
|
||||||
.Select(i => new ServerItem(i, null))
|
.Select(i => new ServerItem(i, null))
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
return new QueryResult<ServerItem>
|
return new QueryResult<ServerItem>(
|
||||||
{
|
startIndex,
|
||||||
TotalRecordCount = result.Count,
|
result.Count,
|
||||||
Items = serverItems
|
serverItems);
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Converts a <see cref="QueryResult{BaseItem}"/> to a <see cref="QueryResult{ServerItem}"/>.
|
/// Converts a <see cref="QueryResult{BaseItem}"/> to a <see cref="QueryResult{ServerItem}"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="startIndex">The index the result started at.</param>
|
||||||
/// <param name="result">A <see cref="QueryResult{BaseItem}"/>.</param>
|
/// <param name="result">A <see cref="QueryResult{BaseItem}"/>.</param>
|
||||||
/// <returns>The <see cref="QueryResult{ServerItem}"/>.</returns>
|
/// <returns>The <see cref="QueryResult{ServerItem}"/>.</returns>
|
||||||
private static QueryResult<ServerItem> ToResult(QueryResult<BaseItem> result)
|
private static QueryResult<ServerItem> ToResult(int? startIndex, QueryResult<BaseItem> result)
|
||||||
{
|
{
|
||||||
var length = result.Items.Count;
|
var length = result.Items.Count;
|
||||||
var serverItems = new ServerItem[length];
|
var serverItems = new ServerItem[length];
|
||||||
@@ -1186,32 +1177,31 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
serverItems[i] = new ServerItem(result.Items[i], null);
|
serverItems[i] = new ServerItem(result.Items[i], null);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new QueryResult<ServerItem>
|
return new QueryResult<ServerItem>(
|
||||||
{
|
startIndex,
|
||||||
TotalRecordCount = result.TotalRecordCount,
|
result.TotalRecordCount,
|
||||||
Items = serverItems
|
serverItems);
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Converts a query result to a <see cref="QueryResult{ServerItem}"/>.
|
/// Converts a query result to a <see cref="QueryResult{ServerItem}"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="startIndex">The start index.</param>
|
||||||
/// <param name="result">A <see cref="QueryResult{BaseItem}"/>.</param>
|
/// <param name="result">A <see cref="QueryResult{BaseItem}"/>.</param>
|
||||||
/// <returns>The <see cref="QueryResult{ServerItem}"/>.</returns>
|
/// <returns>The <see cref="QueryResult{ServerItem}"/>.</returns>
|
||||||
private static QueryResult<ServerItem> ToResult(QueryResult<(BaseItem, ItemCounts)> result)
|
private static QueryResult<ServerItem> ToResult(int? startIndex, QueryResult<(BaseItem Item, ItemCounts ItemCounts)> result)
|
||||||
{
|
{
|
||||||
var length = result.Items.Count;
|
var length = result.Items.Count;
|
||||||
var serverItems = new ServerItem[length];
|
var serverItems = new ServerItem[length];
|
||||||
for (var i = 0; i < length; i++)
|
for (var i = 0; i < length; i++)
|
||||||
{
|
{
|
||||||
serverItems[i] = new ServerItem(result.Items[i].Item1, null);
|
serverItems[i] = new ServerItem(result.Items[i].Item, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new QueryResult<ServerItem>
|
return new QueryResult<ServerItem>(
|
||||||
{
|
startIndex,
|
||||||
TotalRecordCount = result.TotalRecordCount,
|
result.TotalRecordCount,
|
||||||
Items = serverItems
|
serverItems);
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -1219,7 +1209,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="sort">The <see cref="SortCriteria"/>.</param>
|
/// <param name="sort">The <see cref="SortCriteria"/>.</param>
|
||||||
/// <param name="isPreSorted">True if pre-sorted.</param>
|
/// <param name="isPreSorted">True if pre-sorted.</param>
|
||||||
private static (string, SortOrder)[] GetOrderBy(SortCriteria sort, bool isPreSorted)
|
private static (string SortName, SortOrder SortOrder)[] GetOrderBy(SortCriteria sort, bool isPreSorted)
|
||||||
{
|
{
|
||||||
return isPreSorted ? Array.Empty<(string, SortOrder)>() : new[] { (ItemSortBy.SortName, sort.SortOrder) };
|
return isPreSorted ? Array.Empty<(string, SortOrder)>() : new[] { (ItemSortBy.SortName, sort.SortOrder) };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -160,7 +160,7 @@ namespace Emby.Dlna.Didl
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
var parent = item.DisplayParentId;
|
var parent = item.DisplayParentId;
|
||||||
if (!parent.Equals(Guid.Empty))
|
if (!parent.Equals(default))
|
||||||
{
|
{
|
||||||
writer.WriteAttributeString("parentID", GetClientId(parent, null));
|
writer.WriteAttributeString("parentID", GetClientId(parent, null));
|
||||||
}
|
}
|
||||||
@@ -657,7 +657,7 @@ namespace Emby.Dlna.Didl
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
var parent = folder.DisplayParentId;
|
var parent = folder.DisplayParentId;
|
||||||
if (parent.Equals(Guid.Empty))
|
if (parent.Equals(default))
|
||||||
{
|
{
|
||||||
writer.WriteAttributeString("parentID", "0");
|
writer.WriteAttributeString("parentID", "0");
|
||||||
}
|
}
|
||||||
@@ -989,7 +989,7 @@ namespace Emby.Dlna.Didl
|
|||||||
writer.WriteAttributeString("dlna", "profileID", NsDlna, _profile.AlbumArtPn);
|
writer.WriteAttributeString("dlna", "profileID", NsDlna, _profile.AlbumArtPn);
|
||||||
}
|
}
|
||||||
|
|
||||||
writer.WriteString(albumArtUrlInfo.url);
|
writer.WriteString(albumArtUrlInfo.Url);
|
||||||
writer.WriteFullEndElement();
|
writer.WriteFullEndElement();
|
||||||
|
|
||||||
// TODO: Remove these default values
|
// TODO: Remove these default values
|
||||||
@@ -998,7 +998,7 @@ namespace Emby.Dlna.Didl
|
|||||||
_profile.MaxIconWidth ?? 48,
|
_profile.MaxIconWidth ?? 48,
|
||||||
_profile.MaxIconHeight ?? 48,
|
_profile.MaxIconHeight ?? 48,
|
||||||
"jpg");
|
"jpg");
|
||||||
writer.WriteElementString("upnp", "icon", NsUpnp, iconUrlInfo.url);
|
writer.WriteElementString("upnp", "icon", NsUpnp, iconUrlInfo.Url);
|
||||||
|
|
||||||
if (!_profile.EnableAlbumArtInDidl)
|
if (!_profile.EnableAlbumArtInDidl)
|
||||||
{
|
{
|
||||||
@@ -1045,8 +1045,8 @@ namespace Emby.Dlna.Didl
|
|||||||
|
|
||||||
// Images must have a reported size or many clients (Bubble upnp), will only use the first thumbnail
|
// Images must have a reported size or many clients (Bubble upnp), will only use the first thumbnail
|
||||||
// rather than using a larger one when available
|
// rather than using a larger one when available
|
||||||
var width = albumartUrlInfo.width ?? maxWidth;
|
var width = albumartUrlInfo.Width ?? maxWidth;
|
||||||
var height = albumartUrlInfo.height ?? maxHeight;
|
var height = albumartUrlInfo.Height ?? maxHeight;
|
||||||
|
|
||||||
var contentFeatures = ContentFeatureBuilder.BuildImageHeader(_profile, format, width, height, imageInfo.IsDirectStream, org_Pn);
|
var contentFeatures = ContentFeatureBuilder.BuildImageHeader(_profile, format, width, height, imageInfo.IsDirectStream, org_Pn);
|
||||||
|
|
||||||
@@ -1062,7 +1062,7 @@ namespace Emby.Dlna.Didl
|
|||||||
"resolution",
|
"resolution",
|
||||||
string.Format(CultureInfo.InvariantCulture, "{0}x{1}", width, height));
|
string.Format(CultureInfo.InvariantCulture, "{0}x{1}", width, height));
|
||||||
|
|
||||||
writer.WriteString(albumartUrlInfo.url);
|
writer.WriteString(albumartUrlInfo.Url);
|
||||||
|
|
||||||
writer.WriteFullEndElement();
|
writer.WriteFullEndElement();
|
||||||
}
|
}
|
||||||
@@ -1200,7 +1200,7 @@ namespace Emby.Dlna.Didl
|
|||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
private (string url, int? width, int? height) GetImageUrl(ImageDownloadInfo info, int maxWidth, int maxHeight, string format)
|
private (string Url, int? Width, int? Height) GetImageUrl(ImageDownloadInfo info, int maxWidth, int maxHeight, string format)
|
||||||
{
|
{
|
||||||
var url = string.Format(
|
var url = string.Format(
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
|
|||||||
@@ -17,8 +17,7 @@ namespace Emby.Dlna.Didl
|
|||||||
public Filter(string filter)
|
public Filter(string filter)
|
||||||
{
|
{
|
||||||
_all = string.Equals(filter, "*", StringComparison.OrdinalIgnoreCase);
|
_all = string.Equals(filter, "*", StringComparison.OrdinalIgnoreCase);
|
||||||
|
_fields = filter.Split(',', StringSplitOptions.RemoveEmptyEntries);
|
||||||
_fields = (filter ?? string.Empty).Split(',', StringSplitOptions.RemoveEmptyEntries);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Contains(string field)
|
public bool Contains(string field)
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ using System.Globalization;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text;
|
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -84,8 +83,7 @@ namespace Emby.Dlna
|
|||||||
{
|
{
|
||||||
lock (_profiles)
|
lock (_profiles)
|
||||||
{
|
{
|
||||||
var list = _profiles.Values.ToList();
|
return _profiles.Values
|
||||||
return list
|
|
||||||
.OrderBy(i => i.Item1.Info.Type == DeviceProfileType.User ? 0 : 1)
|
.OrderBy(i => i.Item1.Info.Type == DeviceProfileType.User ? 0 : 1)
|
||||||
.ThenBy(i => i.Item1.Info.Name)
|
.ThenBy(i => i.Item1.Info.Name)
|
||||||
.Select(i => i.Item2)
|
.Select(i => i.Item2)
|
||||||
@@ -227,11 +225,8 @@ namespace Emby.Dlna
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var xmlFies = _fileSystem.GetFilePaths(path)
|
return _fileSystem.GetFilePaths(path)
|
||||||
.Where(i => string.Equals(Path.GetExtension(i), ".xml", StringComparison.OrdinalIgnoreCase))
|
.Where(i => string.Equals(Path.GetExtension(i), ".xml", StringComparison.OrdinalIgnoreCase))
|
||||||
.ToList();
|
|
||||||
|
|
||||||
return xmlFies
|
|
||||||
.Select(i => ParseProfileFile(i, type))
|
.Select(i => ParseProfileFile(i, type))
|
||||||
.Where(i => i != null)
|
.Where(i => i != null)
|
||||||
.ToList()!; // We just filtered out all the nulls
|
.ToList()!; // We just filtered out all the nulls
|
||||||
@@ -253,11 +248,8 @@ namespace Emby.Dlna
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
DeviceProfile profile;
|
|
||||||
|
|
||||||
var tempProfile = (DeviceProfile)_xmlSerializer.DeserializeFromFile(typeof(DeviceProfile), path);
|
var tempProfile = (DeviceProfile)_xmlSerializer.DeserializeFromFile(typeof(DeviceProfile), path);
|
||||||
|
var profile = ReserializeProfile(tempProfile);
|
||||||
profile = ReserializeProfile(tempProfile);
|
|
||||||
|
|
||||||
profile.Id = path.ToLowerInvariant().GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
profile.Id = path.ToLowerInvariant().GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
@@ -296,8 +288,7 @@ namespace Emby.Dlna
|
|||||||
{
|
{
|
||||||
lock (_profiles)
|
lock (_profiles)
|
||||||
{
|
{
|
||||||
var list = _profiles.Values.ToList();
|
return _profiles.Values
|
||||||
return list
|
|
||||||
.Select(i => i.Item1)
|
.Select(i => i.Item1)
|
||||||
.OrderBy(i => i.Info.Type == DeviceProfileType.User ? 0 : 1)
|
.OrderBy(i => i.Info.Type == DeviceProfileType.User ? 0 : 1)
|
||||||
.ThenBy(i => i.Info.Name);
|
.ThenBy(i => i.Info.Name);
|
||||||
@@ -350,9 +341,10 @@ namespace Emby.Dlna
|
|||||||
Directory.CreateDirectory(systemProfilesPath);
|
Directory.CreateDirectory(systemProfilesPath);
|
||||||
|
|
||||||
var fileOptions = AsyncFile.WriteOptions;
|
var fileOptions = AsyncFile.WriteOptions;
|
||||||
fileOptions.Mode = FileMode.CreateNew;
|
fileOptions.Mode = FileMode.Create;
|
||||||
fileOptions.PreallocationSize = length;
|
fileOptions.PreallocationSize = length;
|
||||||
using (var fileStream = new FileStream(path, fileOptions))
|
var fileStream = new FileStream(path, fileOptions);
|
||||||
|
await using (fileStream.ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
await stream.CopyToAsync(fileStream).ConfigureAwait(false);
|
await stream.CopyToAsync(fileStream).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
@@ -464,7 +456,7 @@ namespace Emby.Dlna
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public string GetServerDescriptionXml(IHeaderDictionary headers, string serverUuId, string serverAddress)
|
public string GetServerDescriptionXml(IHeaderDictionary headers, string serverUuId, string serverAddress)
|
||||||
{
|
{
|
||||||
var profile = GetDefaultProfile();
|
var profile = GetProfile(headers) ?? GetDefaultProfile();
|
||||||
|
|
||||||
var serverId = _appHost.SystemId;
|
var serverId = _appHost.SystemId;
|
||||||
|
|
||||||
|
|||||||
@@ -22,10 +22,18 @@
|
|||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||||
|
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
<!-- Code Analyzers-->
|
<!-- Code Analyzers-->
|
||||||
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||||
|
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.3">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
|
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
|
||||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
|
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.406" PrivateAssets="All" />
|
||||||
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
|
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ namespace Emby.Dlna.Main
|
|||||||
config);
|
config);
|
||||||
Current = this;
|
Current = this;
|
||||||
|
|
||||||
var netConfig = config.GetConfiguration<NetworkConfiguration>("network");
|
var netConfig = config.GetConfiguration<NetworkConfiguration>(NetworkConfigurationStore.StoreKey);
|
||||||
_disabled = appHost.ListenWithHttps && netConfig.RequireHttps;
|
_disabled = appHost.ListenWithHttps && netConfig.RequireHttps;
|
||||||
|
|
||||||
if (_disabled && _config.GetDlnaConfiguration().EnableServer)
|
if (_disabled && _config.GetDlnaConfiguration().EnableServer)
|
||||||
@@ -262,7 +262,6 @@ namespace Emby.Dlna.Main
|
|||||||
{
|
{
|
||||||
_publisher = new SsdpDevicePublisher(
|
_publisher = new SsdpDevicePublisher(
|
||||||
_communicationsServer,
|
_communicationsServer,
|
||||||
_networkManager,
|
|
||||||
MediaBrowser.Common.System.OperatingSystem.Name,
|
MediaBrowser.Common.System.OperatingSystem.Name,
|
||||||
Environment.OSVersion.VersionString,
|
Environment.OSVersion.VersionString,
|
||||||
_config.GetDlnaConfiguration().SendOnlyMatchedHost)
|
_config.GetDlnaConfiguration().SendOnlyMatchedHost)
|
||||||
@@ -363,7 +362,7 @@ namespace Emby.Dlna.Main
|
|||||||
guid = text.GetMD5();
|
guid = text.GetMD5();
|
||||||
}
|
}
|
||||||
|
|
||||||
return guid.ToString("N", CultureInfo.InvariantCulture);
|
return guid.ToString("D", CultureInfo.InvariantCulture);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetProperies(SsdpDevice device, string fullDeviceType)
|
private void SetProperies(SsdpDevice device, string fullDeviceType)
|
||||||
@@ -400,7 +399,6 @@ namespace Emby.Dlna.Main
|
|||||||
_imageProcessor,
|
_imageProcessor,
|
||||||
_deviceDiscovery,
|
_deviceDiscovery,
|
||||||
_httpClientFactory,
|
_httpClientFactory,
|
||||||
_config,
|
|
||||||
_userDataManager,
|
_userDataManager,
|
||||||
_localization,
|
_localization,
|
||||||
_mediaSourceManager,
|
_mediaSourceManager,
|
||||||
|
|||||||
@@ -69,11 +69,11 @@ namespace Emby.Dlna.PlayTo
|
|||||||
|
|
||||||
public TransportState TransportState { get; private set; }
|
public TransportState TransportState { get; private set; }
|
||||||
|
|
||||||
public bool IsPlaying => TransportState == TransportState.Playing;
|
public bool IsPlaying => TransportState == TransportState.PLAYING;
|
||||||
|
|
||||||
public bool IsPaused => TransportState == TransportState.Paused || TransportState == TransportState.PausedPlayback;
|
public bool IsPaused => TransportState == TransportState.PAUSED_PLAYBACK;
|
||||||
|
|
||||||
public bool IsStopped => TransportState == TransportState.Stopped;
|
public bool IsStopped => TransportState == TransportState.STOPPED;
|
||||||
|
|
||||||
public Action OnDeviceUnavailable { get; set; }
|
public Action OnDeviceUnavailable { get; set; }
|
||||||
|
|
||||||
@@ -494,7 +494,7 @@ namespace Emby.Dlna.PlayTo
|
|||||||
cancellationToken: cancellationToken)
|
cancellationToken: cancellationToken)
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
TransportState = TransportState.Paused;
|
TransportState = TransportState.PAUSED_PLAYBACK;
|
||||||
|
|
||||||
RestartTimer(true);
|
RestartTimer(true);
|
||||||
}
|
}
|
||||||
@@ -527,7 +527,7 @@ namespace Emby.Dlna.PlayTo
|
|||||||
if (transportState.HasValue)
|
if (transportState.HasValue)
|
||||||
{
|
{
|
||||||
// If we're not playing anything no need to get additional data
|
// If we're not playing anything no need to get additional data
|
||||||
if (transportState.Value == TransportState.Stopped)
|
if (transportState.Value == TransportState.STOPPED)
|
||||||
{
|
{
|
||||||
UpdateMediaInfo(null, transportState.Value);
|
UpdateMediaInfo(null, transportState.Value);
|
||||||
}
|
}
|
||||||
@@ -535,9 +535,9 @@ namespace Emby.Dlna.PlayTo
|
|||||||
{
|
{
|
||||||
var tuple = await GetPositionInfo(avCommands, cancellationToken).ConfigureAwait(false);
|
var tuple = await GetPositionInfo(avCommands, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
var currentObject = tuple.Item2;
|
var currentObject = tuple.Track;
|
||||||
|
|
||||||
if (tuple.Item1 && currentObject == null)
|
if (tuple.Success && currentObject == null)
|
||||||
{
|
{
|
||||||
currentObject = await GetMediaInfo(avCommands, cancellationToken).ConfigureAwait(false);
|
currentObject = await GetMediaInfo(avCommands, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
@@ -556,7 +556,7 @@ namespace Emby.Dlna.PlayTo
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If we're not playing anything make sure we don't get data more often than necessary to keep the Session alive
|
// If we're not playing anything make sure we don't get data more often than necessary to keep the Session alive
|
||||||
if (transportState.Value == TransportState.Stopped)
|
if (transportState.Value == TransportState.STOPPED)
|
||||||
{
|
{
|
||||||
RestartTimerInactive();
|
RestartTimerInactive();
|
||||||
}
|
}
|
||||||
@@ -797,7 +797,7 @@ namespace Emby.Dlna.PlayTo
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<(bool, UBaseObject)> GetPositionInfo(TransportCommands avCommands, CancellationToken cancellationToken)
|
private async Task<(bool Success, UBaseObject Track)> GetPositionInfo(TransportCommands avCommands, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetPositionInfo");
|
var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetPositionInfo");
|
||||||
if (command == null)
|
if (command == null)
|
||||||
@@ -1179,6 +1179,7 @@ namespace Emby.Dlna.PlayTo
|
|||||||
return new Device(deviceProperties, httpClientFactory, logger);
|
return new Device(deviceProperties, httpClientFactory, logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
private static DeviceIcon CreateIcon(XElement element)
|
private static DeviceIcon CreateIcon(XElement element)
|
||||||
{
|
{
|
||||||
if (element == null)
|
if (element == null)
|
||||||
@@ -1186,69 +1187,61 @@ namespace Emby.Dlna.PlayTo
|
|||||||
throw new ArgumentNullException(nameof(element));
|
throw new ArgumentNullException(nameof(element));
|
||||||
}
|
}
|
||||||
|
|
||||||
var mimeType = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("mimetype"));
|
|
||||||
var width = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("width"));
|
var width = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("width"));
|
||||||
var height = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("height"));
|
var height = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("height"));
|
||||||
var depth = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("depth"));
|
|
||||||
var url = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("url"));
|
|
||||||
|
|
||||||
var widthValue = int.Parse(width, NumberStyles.Integer, CultureInfo.InvariantCulture);
|
_ = int.TryParse(width, NumberStyles.Integer, CultureInfo.InvariantCulture, out var widthValue);
|
||||||
var heightValue = int.Parse(height, NumberStyles.Integer, CultureInfo.InvariantCulture);
|
_ = int.TryParse(height, NumberStyles.Integer, CultureInfo.InvariantCulture, out var heightValue);
|
||||||
|
|
||||||
return new DeviceIcon
|
return new DeviceIcon
|
||||||
{
|
{
|
||||||
Depth = depth,
|
Depth = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("depth")) ?? string.Empty,
|
||||||
Height = heightValue,
|
Height = heightValue,
|
||||||
MimeType = mimeType,
|
MimeType = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("mimetype")) ?? string.Empty,
|
||||||
Url = url,
|
Url = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("url")) ?? string.Empty,
|
||||||
Width = widthValue
|
Width = widthValue
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static DeviceService Create(XElement element)
|
private static DeviceService Create(XElement element)
|
||||||
{
|
=> new DeviceService()
|
||||||
var type = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("serviceType"));
|
|
||||||
var id = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("serviceId"));
|
|
||||||
var scpdUrl = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("SCPDURL"));
|
|
||||||
var controlURL = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("controlURL"));
|
|
||||||
var eventSubURL = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("eventSubURL"));
|
|
||||||
|
|
||||||
return new DeviceService
|
|
||||||
{
|
{
|
||||||
ControlUrl = controlURL,
|
ControlUrl = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("controlURL")) ?? string.Empty,
|
||||||
EventSubUrl = eventSubURL,
|
EventSubUrl = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("eventSubURL")) ?? string.Empty,
|
||||||
ScpdUrl = scpdUrl,
|
ScpdUrl = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("SCPDURL")) ?? string.Empty,
|
||||||
ServiceId = id,
|
ServiceId = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("serviceId")) ?? string.Empty,
|
||||||
ServiceType = type
|
ServiceType = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("serviceType")) ?? string.Empty
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateMediaInfo(UBaseObject mediaInfo, TransportState state)
|
private void UpdateMediaInfo(UBaseObject? mediaInfo, TransportState state)
|
||||||
{
|
{
|
||||||
TransportState = state;
|
TransportState = state;
|
||||||
|
|
||||||
var previousMediaInfo = CurrentMediaInfo;
|
var previousMediaInfo = CurrentMediaInfo;
|
||||||
CurrentMediaInfo = mediaInfo;
|
CurrentMediaInfo = mediaInfo;
|
||||||
|
|
||||||
if (previousMediaInfo == null && mediaInfo != null)
|
if (mediaInfo == null)
|
||||||
{
|
{
|
||||||
if (state != TransportState.Stopped)
|
if (previousMediaInfo != null)
|
||||||
|
{
|
||||||
|
OnPlaybackStop(previousMediaInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (previousMediaInfo == null)
|
||||||
|
{
|
||||||
|
if (state != TransportState.STOPPED)
|
||||||
{
|
{
|
||||||
OnPlaybackStart(mediaInfo);
|
OnPlaybackStart(mediaInfo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (mediaInfo != null && previousMediaInfo != null && !mediaInfo.Equals(previousMediaInfo))
|
else if (mediaInfo.Equals(previousMediaInfo))
|
||||||
{
|
|
||||||
OnMediaChanged(previousMediaInfo, mediaInfo);
|
|
||||||
}
|
|
||||||
else if (mediaInfo == null && previousMediaInfo != null)
|
|
||||||
{
|
|
||||||
OnPlaybackStop(previousMediaInfo);
|
|
||||||
}
|
|
||||||
else if (mediaInfo != null && mediaInfo.Equals(previousMediaInfo))
|
|
||||||
{
|
{
|
||||||
OnPlaybackProgress(mediaInfo);
|
OnPlaybackProgress(mediaInfo);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
OnMediaChanged(previousMediaInfo, mediaInfo);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPlaybackStart(UBaseObject mediaInfo)
|
private void OnPlaybackStart(UBaseObject mediaInfo)
|
||||||
|
|||||||
@@ -174,13 +174,13 @@ namespace Emby.Dlna.PlayTo
|
|||||||
await _sessionManager.OnPlaybackStart(newItemProgress).ConfigureAwait(false);
|
await _sessionManager.OnPlaybackStart(newItemProgress).ConfigureAwait(false);
|
||||||
|
|
||||||
// Send a message to the DLNA device to notify what is the next track in the playlist.
|
// Send a message to the DLNA device to notify what is the next track in the playlist.
|
||||||
var currentItemIndex = _playlist.FindIndex(item => item.StreamInfo.ItemId == streamInfo.ItemId);
|
var currentItemIndex = _playlist.FindIndex(item => item.StreamInfo.ItemId.Equals(streamInfo.ItemId));
|
||||||
if (currentItemIndex >= 0)
|
if (currentItemIndex >= 0)
|
||||||
{
|
{
|
||||||
_currentPlaylistIndex = currentItemIndex;
|
_currentPlaylistIndex = currentItemIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
await SendNextTrackMessage(currentItemIndex, CancellationToken.None);
|
await SendNextTrackMessage(currentItemIndex, CancellationToken.None).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -210,9 +210,9 @@ namespace Emby.Dlna.PlayTo
|
|||||||
|
|
||||||
var mediaSource = await streamInfo.GetMediaSource(CancellationToken.None).ConfigureAwait(false);
|
var mediaSource = await streamInfo.GetMediaSource(CancellationToken.None).ConfigureAwait(false);
|
||||||
|
|
||||||
var duration = mediaSource == null ?
|
var duration = mediaSource == null
|
||||||
(_device.Duration == null ? (long?)null : _device.Duration.Value.Ticks) :
|
? _device.Duration?.Ticks
|
||||||
mediaSource.RunTimeTicks;
|
: mediaSource.RunTimeTicks;
|
||||||
|
|
||||||
var playedToCompletion = positionTicks.HasValue && positionTicks.Value == 0;
|
var playedToCompletion = positionTicks.HasValue && positionTicks.Value == 0;
|
||||||
|
|
||||||
@@ -349,7 +349,9 @@ namespace Emby.Dlna.PlayTo
|
|||||||
{
|
{
|
||||||
_logger.LogDebug("{0} - Received PlayRequest: {1}", _session.DeviceName, command.PlayCommand);
|
_logger.LogDebug("{0} - Received PlayRequest: {1}", _session.DeviceName, command.PlayCommand);
|
||||||
|
|
||||||
var user = command.ControllingUserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(command.ControllingUserId);
|
var user = command.ControllingUserId.Equals(default)
|
||||||
|
? null :
|
||||||
|
_userManager.GetUserById(command.ControllingUserId);
|
||||||
|
|
||||||
var items = new List<BaseItem>();
|
var items = new List<BaseItem>();
|
||||||
foreach (var id in command.ItemIds)
|
foreach (var id in command.ItemIds)
|
||||||
@@ -392,7 +394,7 @@ namespace Emby.Dlna.PlayTo
|
|||||||
_playlist.AddRange(playlist);
|
_playlist.AddRange(playlist);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!command.ControllingUserId.Equals(Guid.Empty))
|
if (!command.ControllingUserId.Equals(default))
|
||||||
{
|
{
|
||||||
_sessionManager.LogSessionActivity(
|
_sessionManager.LogSessionActivity(
|
||||||
_session.Client,
|
_session.Client,
|
||||||
@@ -446,14 +448,16 @@ namespace Emby.Dlna.PlayTo
|
|||||||
|
|
||||||
if (info.Item != null && !EnableClientSideSeek(info))
|
if (info.Item != null && !EnableClientSideSeek(info))
|
||||||
{
|
{
|
||||||
var user = !_session.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(_session.UserId) : null;
|
var user = _session.UserId.Equals(default)
|
||||||
|
? null
|
||||||
|
: _userManager.GetUserById(_session.UserId);
|
||||||
var newItem = CreatePlaylistItem(info.Item, user, newPosition, info.MediaSourceId, info.AudioStreamIndex, info.SubtitleStreamIndex);
|
var newItem = CreatePlaylistItem(info.Item, user, newPosition, info.MediaSourceId, info.AudioStreamIndex, info.SubtitleStreamIndex);
|
||||||
|
|
||||||
await _device.SetAvTransport(newItem.StreamUrl, GetDlnaHeaders(newItem), newItem.Didl, CancellationToken.None).ConfigureAwait(false);
|
await _device.SetAvTransport(newItem.StreamUrl, GetDlnaHeaders(newItem), newItem.Didl, CancellationToken.None).ConfigureAwait(false);
|
||||||
|
|
||||||
// Send a message to the DLNA device to notify what is the next track in the play list.
|
// Send a message to the DLNA device to notify what is the next track in the play list.
|
||||||
var newItemIndex = _playlist.FindIndex(item => item.StreamUrl == newItem.StreamUrl);
|
var newItemIndex = _playlist.FindIndex(item => item.StreamUrl == newItem.StreamUrl);
|
||||||
await SendNextTrackMessage(newItemIndex, CancellationToken.None);
|
await SendNextTrackMessage(newItemIndex, CancellationToken.None).ConfigureAwait(false);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -569,7 +573,7 @@ namespace Emby.Dlna.PlayTo
|
|||||||
streamInfo.TargetVideoCodecTag,
|
streamInfo.TargetVideoCodecTag,
|
||||||
streamInfo.IsTargetAVC);
|
streamInfo.IsTargetAVC);
|
||||||
|
|
||||||
return list.Count == 0 ? null : list[0];
|
return list.FirstOrDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@@ -654,7 +658,7 @@ namespace Emby.Dlna.PlayTo
|
|||||||
await _device.SetAvTransport(currentitem.StreamUrl, GetDlnaHeaders(currentitem), currentitem.Didl, cancellationToken).ConfigureAwait(false);
|
await _device.SetAvTransport(currentitem.StreamUrl, GetDlnaHeaders(currentitem), currentitem.Didl, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
// Send a message to the DLNA device to notify what is the next track in the play list.
|
// Send a message to the DLNA device to notify what is the next track in the play list.
|
||||||
await SendNextTrackMessage(index, cancellationToken);
|
await SendNextTrackMessage(index, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
var streamInfo = currentitem.StreamInfo;
|
var streamInfo = currentitem.StreamInfo;
|
||||||
if (streamInfo.StartPositionTicks > 0 && EnableClientSideSeek(streamInfo))
|
if (streamInfo.StartPositionTicks > 0 && EnableClientSideSeek(streamInfo))
|
||||||
@@ -764,14 +768,16 @@ namespace Emby.Dlna.PlayTo
|
|||||||
{
|
{
|
||||||
var newPosition = GetProgressPositionTicks(info) ?? 0;
|
var newPosition = GetProgressPositionTicks(info) ?? 0;
|
||||||
|
|
||||||
var user = !_session.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(_session.UserId) : null;
|
var user = _session.UserId.Equals(default)
|
||||||
|
? null
|
||||||
|
: _userManager.GetUserById(_session.UserId);
|
||||||
var newItem = CreatePlaylistItem(info.Item, user, newPosition, info.MediaSourceId, newIndex, info.SubtitleStreamIndex);
|
var newItem = CreatePlaylistItem(info.Item, user, newPosition, info.MediaSourceId, newIndex, info.SubtitleStreamIndex);
|
||||||
|
|
||||||
await _device.SetAvTransport(newItem.StreamUrl, GetDlnaHeaders(newItem), newItem.Didl, CancellationToken.None).ConfigureAwait(false);
|
await _device.SetAvTransport(newItem.StreamUrl, GetDlnaHeaders(newItem), newItem.Didl, CancellationToken.None).ConfigureAwait(false);
|
||||||
|
|
||||||
// Send a message to the DLNA device to notify what is the next track in the play list.
|
// Send a message to the DLNA device to notify what is the next track in the play list.
|
||||||
var newItemIndex = _playlist.FindIndex(item => item.StreamUrl == newItem.StreamUrl);
|
var newItemIndex = _playlist.FindIndex(item => item.StreamUrl == newItem.StreamUrl);
|
||||||
await SendNextTrackMessage(newItemIndex, CancellationToken.None);
|
await SendNextTrackMessage(newItemIndex, CancellationToken.None).ConfigureAwait(false);
|
||||||
|
|
||||||
if (EnableClientSideSeek(newItem.StreamInfo))
|
if (EnableClientSideSeek(newItem.StreamInfo))
|
||||||
{
|
{
|
||||||
@@ -793,14 +799,16 @@ namespace Emby.Dlna.PlayTo
|
|||||||
{
|
{
|
||||||
var newPosition = GetProgressPositionTicks(info) ?? 0;
|
var newPosition = GetProgressPositionTicks(info) ?? 0;
|
||||||
|
|
||||||
var user = !_session.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(_session.UserId) : null;
|
var user = _session.UserId.Equals(default)
|
||||||
|
? null
|
||||||
|
: _userManager.GetUserById(_session.UserId);
|
||||||
var newItem = CreatePlaylistItem(info.Item, user, newPosition, info.MediaSourceId, info.AudioStreamIndex, newIndex);
|
var newItem = CreatePlaylistItem(info.Item, user, newPosition, info.MediaSourceId, info.AudioStreamIndex, newIndex);
|
||||||
|
|
||||||
await _device.SetAvTransport(newItem.StreamUrl, GetDlnaHeaders(newItem), newItem.Didl, CancellationToken.None).ConfigureAwait(false);
|
await _device.SetAvTransport(newItem.StreamUrl, GetDlnaHeaders(newItem), newItem.Didl, CancellationToken.None).ConfigureAwait(false);
|
||||||
|
|
||||||
// Send a message to the DLNA device to notify what is the next track in the play list.
|
// Send a message to the DLNA device to notify what is the next track in the play list.
|
||||||
var newItemIndex = _playlist.FindIndex(item => item.StreamUrl == newItem.StreamUrl);
|
var newItemIndex = _playlist.FindIndex(item => item.StreamUrl == newItem.StreamUrl);
|
||||||
await SendNextTrackMessage(newItemIndex, CancellationToken.None);
|
await SendNextTrackMessage(newItemIndex, CancellationToken.None).ConfigureAwait(false);
|
||||||
|
|
||||||
if (EnableClientSideSeek(newItem.StreamInfo) && newPosition > 0)
|
if (EnableClientSideSeek(newItem.StreamInfo) && newPosition > 0)
|
||||||
{
|
{
|
||||||
@@ -816,7 +824,7 @@ namespace Emby.Dlna.PlayTo
|
|||||||
const int Interval = 500;
|
const int Interval = 500;
|
||||||
|
|
||||||
var currentWait = 0;
|
var currentWait = 0;
|
||||||
while (_device.TransportState != TransportState.Playing && currentWait < MaxWait)
|
while (_device.TransportState != TransportState.PLAYING && currentWait < MaxWait)
|
||||||
{
|
{
|
||||||
await Task.Delay(Interval, cancellationToken).ConfigureAwait(false);
|
await Task.Delay(Interval, cancellationToken).ConfigureAwait(false);
|
||||||
currentWait += Interval;
|
currentWait += Interval;
|
||||||
@@ -883,7 +891,7 @@ namespace Emby.Dlna.PlayTo
|
|||||||
|
|
||||||
private class StreamParams
|
private class StreamParams
|
||||||
{
|
{
|
||||||
private MediaSourceInfo mediaSource;
|
private MediaSourceInfo _mediaSource;
|
||||||
private IMediaSourceManager _mediaSourceManager;
|
private IMediaSourceManager _mediaSourceManager;
|
||||||
|
|
||||||
public Guid ItemId { get; set; }
|
public Guid ItemId { get; set; }
|
||||||
@@ -908,24 +916,22 @@ namespace Emby.Dlna.PlayTo
|
|||||||
|
|
||||||
public async Task<MediaSourceInfo> GetMediaSource(CancellationToken cancellationToken)
|
public async Task<MediaSourceInfo> GetMediaSource(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (mediaSource != null)
|
if (_mediaSource != null)
|
||||||
{
|
{
|
||||||
return mediaSource;
|
return _mediaSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
var hasMediaSources = Item as IHasMediaSources;
|
if (Item is not IHasMediaSources)
|
||||||
|
|
||||||
if (hasMediaSources == null)
|
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_mediaSourceManager != null)
|
if (_mediaSourceManager != null)
|
||||||
{
|
{
|
||||||
mediaSource = await _mediaSourceManager.GetMediaSource(Item, MediaSourceId, LiveStreamId, false, cancellationToken).ConfigureAwait(false);
|
_mediaSource = await _mediaSourceManager.GetMediaSource(Item, MediaSourceId, LiveStreamId, false, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
return mediaSource;
|
return _mediaSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Guid GetItemId(string url)
|
private static Guid GetItemId(string url)
|
||||||
@@ -951,7 +957,7 @@ namespace Emby.Dlna.PlayTo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Guid.Empty;
|
return default;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static StreamParams ParseFromUrl(string url, ILibraryManager libraryManager, IMediaSourceManager mediaSourceManager)
|
public static StreamParams ParseFromUrl(string url, ILibraryManager libraryManager, IMediaSourceManager mediaSourceManager)
|
||||||
@@ -966,7 +972,7 @@ namespace Emby.Dlna.PlayTo
|
|||||||
ItemId = GetItemId(url)
|
ItemId = GetItemId(url)
|
||||||
};
|
};
|
||||||
|
|
||||||
if (request.ItemId.Equals(Guid.Empty))
|
if (request.ItemId.Equals(default))
|
||||||
{
|
{
|
||||||
return request;
|
return request;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ using System.Threading.Tasks;
|
|||||||
using Jellyfin.Data.Events;
|
using Jellyfin.Data.Events;
|
||||||
using MediaBrowser.Common.Extensions;
|
using MediaBrowser.Common.Extensions;
|
||||||
using MediaBrowser.Controller;
|
using MediaBrowser.Controller;
|
||||||
using MediaBrowser.Controller.Configuration;
|
|
||||||
using MediaBrowser.Controller.Dlna;
|
using MediaBrowser.Controller.Dlna;
|
||||||
using MediaBrowser.Controller.Drawing;
|
using MediaBrowser.Controller.Drawing;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
@@ -35,7 +34,6 @@ namespace Emby.Dlna.PlayTo
|
|||||||
private readonly IServerApplicationHost _appHost;
|
private readonly IServerApplicationHost _appHost;
|
||||||
private readonly IImageProcessor _imageProcessor;
|
private readonly IImageProcessor _imageProcessor;
|
||||||
private readonly IHttpClientFactory _httpClientFactory;
|
private readonly IHttpClientFactory _httpClientFactory;
|
||||||
private readonly IServerConfigurationManager _config;
|
|
||||||
private readonly IUserDataManager _userDataManager;
|
private readonly IUserDataManager _userDataManager;
|
||||||
private readonly ILocalizationManager _localization;
|
private readonly ILocalizationManager _localization;
|
||||||
|
|
||||||
@@ -47,7 +45,7 @@ namespace Emby.Dlna.PlayTo
|
|||||||
private SemaphoreSlim _sessionLock = new SemaphoreSlim(1, 1);
|
private SemaphoreSlim _sessionLock = new SemaphoreSlim(1, 1);
|
||||||
private CancellationTokenSource _disposeCancellationTokenSource = new CancellationTokenSource();
|
private CancellationTokenSource _disposeCancellationTokenSource = new CancellationTokenSource();
|
||||||
|
|
||||||
public PlayToManager(ILogger logger, ISessionManager sessionManager, ILibraryManager libraryManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost, IImageProcessor imageProcessor, IDeviceDiscovery deviceDiscovery, IHttpClientFactory httpClientFactory, IServerConfigurationManager config, IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, IMediaEncoder mediaEncoder)
|
public PlayToManager(ILogger logger, ISessionManager sessionManager, ILibraryManager libraryManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost, IImageProcessor imageProcessor, IDeviceDiscovery deviceDiscovery, IHttpClientFactory httpClientFactory, IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, IMediaEncoder mediaEncoder)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_sessionManager = sessionManager;
|
_sessionManager = sessionManager;
|
||||||
@@ -58,7 +56,6 @@ namespace Emby.Dlna.PlayTo
|
|||||||
_imageProcessor = imageProcessor;
|
_imageProcessor = imageProcessor;
|
||||||
_deviceDiscovery = deviceDiscovery;
|
_deviceDiscovery = deviceDiscovery;
|
||||||
_httpClientFactory = httpClientFactory;
|
_httpClientFactory = httpClientFactory;
|
||||||
_config = config;
|
|
||||||
_userDataManager = userDataManager;
|
_userDataManager = userDataManager;
|
||||||
_localization = localization;
|
_localization = localization;
|
||||||
_mediaSourceManager = mediaSourceManager;
|
_mediaSourceManager = mediaSourceManager;
|
||||||
|
|||||||
@@ -175,7 +175,7 @@ namespace Emby.Dlna.PlayTo
|
|||||||
var sendValue = state.AllowedValues.FirstOrDefault(a => string.Equals(a, commandParameter, StringComparison.OrdinalIgnoreCase)) ??
|
var sendValue = state.AllowedValues.FirstOrDefault(a => string.Equals(a, commandParameter, StringComparison.OrdinalIgnoreCase)) ??
|
||||||
(state.AllowedValues.Count > 0 ? state.AllowedValues[0] : value);
|
(state.AllowedValues.Count > 0 ? state.AllowedValues[0] : value);
|
||||||
|
|
||||||
return string.Format(CultureInfo.InvariantCulture, "<{0} xmlns:dt=\"urn:schemas-microsoft-com:datatypes\" dt:dt=\"{1}\">{2}</{0}>", argument.Name, state.DataType ?? "string", sendValue);
|
return string.Format(CultureInfo.InvariantCulture, "<{0} xmlns:dt=\"urn:schemas-microsoft-com:datatypes\" dt:dt=\"{1}\">{2}</{0}>", argument.Name, state.DataType, sendValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
return string.Format(CultureInfo.InvariantCulture, "<{0}>{1}</{0}>", argument.Name, value);
|
return string.Format(CultureInfo.InvariantCulture, "<{0}>{1}</{0}>", argument.Name, value);
|
||||||
|
|||||||
@@ -2,12 +2,15 @@
|
|||||||
|
|
||||||
namespace Emby.Dlna.PlayTo
|
namespace Emby.Dlna.PlayTo
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Core of the AVTransport service. It defines the conceptually top-
|
||||||
|
/// level state of the transport, for example, whether it is playing, recording, etc.
|
||||||
|
/// </summary>
|
||||||
public enum TransportState
|
public enum TransportState
|
||||||
{
|
{
|
||||||
Stopped,
|
STOPPED,
|
||||||
Playing,
|
PLAYING,
|
||||||
Transitioning,
|
TRANSITIONING,
|
||||||
PausedPlayback,
|
PAUSED_PLAYBACK
|
||||||
Paused
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -167,8 +167,7 @@ namespace Emby.Dlna.Profiles
|
|||||||
|
|
||||||
public void AddXmlRootAttribute(string name, string value)
|
public void AddXmlRootAttribute(string name, string value)
|
||||||
{
|
{
|
||||||
var atts = XmlRootAttributes ?? System.Array.Empty<XmlAttribute>();
|
var list = XmlRootAttributes.ToList();
|
||||||
var list = atts.ToList();
|
|
||||||
|
|
||||||
list.Add(new XmlAttribute
|
list.Add(new XmlAttribute
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -189,7 +189,7 @@ namespace Emby.Dlna.Server
|
|||||||
builder.Append("<icon>");
|
builder.Append("<icon>");
|
||||||
|
|
||||||
builder.Append("<mimetype>")
|
builder.Append("<mimetype>")
|
||||||
.Append(SecurityElement.Escape(icon.MimeType ?? string.Empty))
|
.Append(SecurityElement.Escape(icon.MimeType))
|
||||||
.Append("</mimetype>");
|
.Append("</mimetype>");
|
||||||
builder.Append("<width>")
|
builder.Append("<width>")
|
||||||
.Append(SecurityElement.Escape(icon.Width.ToString(CultureInfo.InvariantCulture)))
|
.Append(SecurityElement.Escape(icon.Width.ToString(CultureInfo.InvariantCulture)))
|
||||||
@@ -198,7 +198,7 @@ namespace Emby.Dlna.Server
|
|||||||
.Append(SecurityElement.Escape(icon.Height.ToString(CultureInfo.InvariantCulture)))
|
.Append(SecurityElement.Escape(icon.Height.ToString(CultureInfo.InvariantCulture)))
|
||||||
.Append("</height>");
|
.Append("</height>");
|
||||||
builder.Append("<depth>")
|
builder.Append("<depth>")
|
||||||
.Append(SecurityElement.Escape(icon.Depth ?? string.Empty))
|
.Append(SecurityElement.Escape(icon.Depth))
|
||||||
.Append("</depth>");
|
.Append("</depth>");
|
||||||
builder.Append("<url>")
|
builder.Append("<url>")
|
||||||
.Append(BuildUrl(icon.Url))
|
.Append(BuildUrl(icon.Url))
|
||||||
@@ -219,10 +219,10 @@ namespace Emby.Dlna.Server
|
|||||||
builder.Append("<service>");
|
builder.Append("<service>");
|
||||||
|
|
||||||
builder.Append("<serviceType>")
|
builder.Append("<serviceType>")
|
||||||
.Append(SecurityElement.Escape(service.ServiceType ?? string.Empty))
|
.Append(SecurityElement.Escape(service.ServiceType))
|
||||||
.Append("</serviceType>");
|
.Append("</serviceType>");
|
||||||
builder.Append("<serviceId>")
|
builder.Append("<serviceId>")
|
||||||
.Append(SecurityElement.Escape(service.ServiceId ?? string.Empty))
|
.Append(SecurityElement.Escape(service.ServiceId))
|
||||||
.Append("</serviceId>");
|
.Append("</serviceId>");
|
||||||
builder.Append("<SCPDURL>")
|
builder.Append("<SCPDURL>")
|
||||||
.Append(BuildUrl(service.ScpdUrl))
|
.Append(BuildUrl(service.ScpdUrl))
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ namespace Emby.Dlna.Service
|
|||||||
|
|
||||||
private async Task<ControlResponse> ProcessControlRequestInternalAsync(ControlRequest request)
|
private async Task<ControlResponse> ProcessControlRequestInternalAsync(ControlRequest request)
|
||||||
{
|
{
|
||||||
ControlRequestInfo? requestInfo = null;
|
ControlRequestInfo requestInfo;
|
||||||
|
|
||||||
using (var streamReader = new StreamReader(request.InputXml, Encoding.UTF8))
|
using (var streamReader = new StreamReader(request.InputXml, Encoding.UTF8))
|
||||||
{
|
{
|
||||||
@@ -66,6 +66,11 @@ namespace Emby.Dlna.Service
|
|||||||
|
|
||||||
Logger.LogDebug("Received control request {LocalName}, params: {@Headers}", requestInfo.LocalName, requestInfo.Headers);
|
Logger.LogDebug("Received control request {LocalName}, params: {@Headers}", requestInfo.LocalName, requestInfo.Headers);
|
||||||
|
|
||||||
|
return CreateControlResponse(requestInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ControlResponse CreateControlResponse(ControlRequestInfo requestInfo)
|
||||||
|
{
|
||||||
var settings = new XmlWriterSettings
|
var settings = new XmlWriterSettings
|
||||||
{
|
{
|
||||||
Encoding = Encoding.UTF8,
|
Encoding = Encoding.UTF8,
|
||||||
@@ -112,29 +117,19 @@ namespace Emby.Dlna.Service
|
|||||||
{
|
{
|
||||||
if (reader.NodeType == XmlNodeType.Element)
|
if (reader.NodeType == XmlNodeType.Element)
|
||||||
{
|
{
|
||||||
switch (reader.LocalName)
|
if (string.Equals(reader.LocalName, "Body", StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
case "Body":
|
if (reader.IsEmptyElement)
|
||||||
{
|
{
|
||||||
if (!reader.IsEmptyElement)
|
await reader.ReadAsync().ConfigureAwait(false);
|
||||||
{
|
continue;
|
||||||
using var subReader = reader.ReadSubtree();
|
}
|
||||||
return await ParseBodyTagAsync(subReader).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
await reader.ReadAsync().ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
using var subReader = reader.ReadSubtree();
|
||||||
}
|
return await ParseBodyTagAsync(subReader).ConfigureAwait(false);
|
||||||
|
|
||||||
default:
|
|
||||||
{
|
|
||||||
await reader.SkipAsync().ConfigureAwait(false);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await reader.SkipAsync().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -160,17 +155,17 @@ namespace Emby.Dlna.Service
|
|||||||
localName = reader.LocalName;
|
localName = reader.LocalName;
|
||||||
namespaceURI = reader.NamespaceURI;
|
namespaceURI = reader.NamespaceURI;
|
||||||
|
|
||||||
if (!reader.IsEmptyElement)
|
if (reader.IsEmptyElement)
|
||||||
|
{
|
||||||
|
await reader.ReadAsync().ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
var result = new ControlRequestInfo(localName, namespaceURI);
|
var result = new ControlRequestInfo(localName, namespaceURI);
|
||||||
using var subReader = reader.ReadSubtree();
|
using var subReader = reader.ReadSubtree();
|
||||||
await ParseFirstBodyChildAsync(subReader, result.Headers).ConfigureAwait(false);
|
await ParseFirstBodyChildAsync(subReader, result.Headers).ConfigureAwait(false);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
await reader.ReadAsync().ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ namespace Emby.Dlna.Service
|
|||||||
builder.Append("<action>");
|
builder.Append("<action>");
|
||||||
|
|
||||||
builder.Append("<name>")
|
builder.Append("<name>")
|
||||||
.Append(SecurityElement.Escape(item.Name ?? string.Empty))
|
.Append(SecurityElement.Escape(item.Name))
|
||||||
.Append("</name>");
|
.Append("</name>");
|
||||||
|
|
||||||
builder.Append("<argumentList>");
|
builder.Append("<argumentList>");
|
||||||
@@ -48,13 +48,13 @@ namespace Emby.Dlna.Service
|
|||||||
builder.Append("<argument>");
|
builder.Append("<argument>");
|
||||||
|
|
||||||
builder.Append("<name>")
|
builder.Append("<name>")
|
||||||
.Append(SecurityElement.Escape(argument.Name ?? string.Empty))
|
.Append(SecurityElement.Escape(argument.Name))
|
||||||
.Append("</name>");
|
.Append("</name>");
|
||||||
builder.Append("<direction>")
|
builder.Append("<direction>")
|
||||||
.Append(SecurityElement.Escape(argument.Direction ?? string.Empty))
|
.Append(SecurityElement.Escape(argument.Direction))
|
||||||
.Append("</direction>");
|
.Append("</direction>");
|
||||||
builder.Append("<relatedStateVariable>")
|
builder.Append("<relatedStateVariable>")
|
||||||
.Append(SecurityElement.Escape(argument.RelatedStateVariable ?? string.Empty))
|
.Append(SecurityElement.Escape(argument.RelatedStateVariable))
|
||||||
.Append("</relatedStateVariable>");
|
.Append("</relatedStateVariable>");
|
||||||
|
|
||||||
builder.Append("</argument>");
|
builder.Append("</argument>");
|
||||||
@@ -81,10 +81,10 @@ namespace Emby.Dlna.Service
|
|||||||
.Append("\">");
|
.Append("\">");
|
||||||
|
|
||||||
builder.Append("<name>")
|
builder.Append("<name>")
|
||||||
.Append(SecurityElement.Escape(item.Name ?? string.Empty))
|
.Append(SecurityElement.Escape(item.Name))
|
||||||
.Append("</name>");
|
.Append("</name>");
|
||||||
builder.Append("<dataType>")
|
builder.Append("<dataType>")
|
||||||
.Append(SecurityElement.Escape(item.DataType ?? string.Empty))
|
.Append(SecurityElement.Escape(item.DataType))
|
||||||
.Append("</dataType>");
|
.Append("</dataType>");
|
||||||
|
|
||||||
if (item.AllowedValues.Count > 0)
|
if (item.AllowedValues.Count > 0)
|
||||||
|
|||||||
@@ -11,6 +11,10 @@
|
|||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||||
|
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
|
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
|
||||||
<ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" />
|
<ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" />
|
||||||
@@ -23,8 +27,12 @@
|
|||||||
|
|
||||||
<!-- Code analysers-->
|
<!-- Code analysers-->
|
||||||
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||||
|
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.3">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
|
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
|
||||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
|
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.406" PrivateAssets="All" />
|
||||||
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
|
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Net.Mime;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Jellyfin.Data.Entities;
|
using Jellyfin.Data.Entities;
|
||||||
@@ -101,8 +102,7 @@ namespace Emby.Drawing
|
|||||||
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);
|
||||||
|
using (var fileStream = AsyncFile.OpenRead(file.Path))
|
||||||
using (var fileStream = AsyncFile.OpenRead(file.Item1))
|
|
||||||
{
|
{
|
||||||
await fileStream.CopyToAsync(toStream).ConfigureAwait(false);
|
await fileStream.CopyToAsync(toStream).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
@@ -117,7 +117,7 @@ namespace Emby.Drawing
|
|||||||
=> _transparentImageTypes.Contains(Path.GetExtension(path));
|
=> _transparentImageTypes.Contains(Path.GetExtension(path));
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <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)
|
||||||
{
|
{
|
||||||
ItemImageInfo originalImage = options.Image;
|
ItemImageInfo originalImage = options.Image;
|
||||||
BaseItem item = options.Item;
|
BaseItem item = options.Item;
|
||||||
@@ -130,20 +130,22 @@ namespace Emby.Drawing
|
|||||||
originalImageSize = new ImageDimensions(originalImage.Width, originalImage.Height);
|
originalImageSize = new ImageDimensions(originalImage.Width, originalImage.Height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var mimeType = MimeTypes.GetMimeType(originalImagePath);
|
||||||
if (!_imageEncoder.SupportsImageEncoding)
|
if (!_imageEncoder.SupportsImageEncoding)
|
||||||
{
|
{
|
||||||
return (originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified);
|
return (originalImagePath, mimeType, dateModified);
|
||||||
}
|
}
|
||||||
|
|
||||||
var supportedImageInfo = await GetSupportedImage(originalImagePath, dateModified).ConfigureAwait(false);
|
var supportedImageInfo = await GetSupportedImage(originalImagePath, dateModified).ConfigureAwait(false);
|
||||||
originalImagePath = supportedImageInfo.path;
|
originalImagePath = supportedImageInfo.Path;
|
||||||
|
|
||||||
if (!File.Exists(originalImagePath))
|
// Original file doesn't exist, or original file is gif.
|
||||||
|
if (!File.Exists(originalImagePath) || string.Equals(mimeType, MediaTypeNames.Image.Gif, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return (originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified);
|
return (originalImagePath, mimeType, dateModified);
|
||||||
}
|
}
|
||||||
|
|
||||||
dateModified = supportedImageInfo.dateModified;
|
dateModified = supportedImageInfo.DateModified;
|
||||||
bool requiresTransparency = _transparentImageTypes.Contains(Path.GetExtension(originalImagePath));
|
bool requiresTransparency = _transparentImageTypes.Contains(Path.GetExtension(originalImagePath));
|
||||||
|
|
||||||
bool autoOrient = false;
|
bool autoOrient = false;
|
||||||
@@ -243,7 +245,7 @@ namespace Emby.Drawing
|
|||||||
return ImageFormat.Jpg;
|
return ImageFormat.Jpg;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string? GetMimeType(ImageFormat format, string path)
|
private string GetMimeType(ImageFormat format, string path)
|
||||||
=> format switch
|
=> format switch
|
||||||
{
|
{
|
||||||
ImageFormat.Bmp => MimeTypes.GetMimeType("i.bmp"),
|
ImageFormat.Bmp => MimeTypes.GetMimeType("i.bmp"),
|
||||||
@@ -437,7 +439,7 @@ namespace Emby.Drawing
|
|||||||
.ToString("N", CultureInfo.InvariantCulture);
|
.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)
|
||||||
{
|
{
|
||||||
var inputFormat = Path.GetExtension(originalImagePath)
|
var inputFormat = Path.GetExtension(originalImagePath)
|
||||||
.TrimStart('.')
|
.TrimStart('.')
|
||||||
|
|||||||
@@ -43,6 +43,12 @@ namespace Emby.Drawing
|
|||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void CreateSplashscreen(IReadOnlyList<string> posters, IReadOnlyList<string> backdrops)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public string GetImageBlurHash(int xComp, int yComp, string path)
|
public string GetImageBlurHash(int xComp, int yComp, string path)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ namespace Emby.Naming.AudioBook
|
|||||||
public class AudioBookListResolver
|
public class AudioBookListResolver
|
||||||
{
|
{
|
||||||
private readonly NamingOptions _options;
|
private readonly NamingOptions _options;
|
||||||
|
private readonly AudioBookResolver _audioBookResolver;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="AudioBookListResolver"/> class.
|
/// Initializes a new instance of the <see cref="AudioBookListResolver"/> class.
|
||||||
@@ -22,6 +23,7 @@ namespace Emby.Naming.AudioBook
|
|||||||
public AudioBookListResolver(NamingOptions options)
|
public AudioBookListResolver(NamingOptions options)
|
||||||
{
|
{
|
||||||
_options = options;
|
_options = options;
|
||||||
|
_audioBookResolver = new AudioBookResolver(_options);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -31,21 +33,18 @@ namespace Emby.Naming.AudioBook
|
|||||||
/// <returns>Returns IEnumerable of <see cref="AudioBookInfo"/>.</returns>
|
/// <returns>Returns IEnumerable of <see cref="AudioBookInfo"/>.</returns>
|
||||||
public IEnumerable<AudioBookInfo> Resolve(IEnumerable<FileSystemMetadata> files)
|
public IEnumerable<AudioBookInfo> Resolve(IEnumerable<FileSystemMetadata> files)
|
||||||
{
|
{
|
||||||
var audioBookResolver = new AudioBookResolver(_options);
|
|
||||||
|
|
||||||
// File with empty fullname will be sorted out here.
|
// File with empty fullname will be sorted out here.
|
||||||
var audiobookFileInfos = files
|
var audiobookFileInfos = files
|
||||||
.Select(i => audioBookResolver.Resolve(i.FullName))
|
.Select(i => _audioBookResolver.Resolve(i.FullName))
|
||||||
.OfType<AudioBookFileInfo>()
|
.OfType<AudioBookFileInfo>()
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
var stackResult = new StackResolver(_options)
|
var stackResult = StackResolver.ResolveAudioBooks(audiobookFileInfos);
|
||||||
.ResolveAudioBooks(audiobookFileInfos);
|
|
||||||
|
|
||||||
foreach (var stack in stackResult)
|
foreach (var stack in stackResult)
|
||||||
{
|
{
|
||||||
var stackFiles = stack.Files
|
var stackFiles = stack.Files
|
||||||
.Select(i => audioBookResolver.Resolve(i))
|
.Select(i => _audioBookResolver.Resolve(i))
|
||||||
.OfType<AudioBookFileInfo>()
|
.OfType<AudioBookFileInfo>()
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
|
||||||
using Emby.Naming.Common;
|
using Emby.Naming.Common;
|
||||||
|
using Jellyfin.Extensions;
|
||||||
|
|
||||||
namespace Emby.Naming.AudioBook
|
namespace Emby.Naming.AudioBook
|
||||||
{
|
{
|
||||||
@@ -37,7 +37,7 @@ namespace Emby.Naming.AudioBook
|
|||||||
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, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#pragma warning disable CA1819
|
#pragma warning disable CA1819
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using Emby.Naming.Video;
|
using Emby.Naming.Video;
|
||||||
@@ -22,47 +23,60 @@ namespace Emby.Naming.Common
|
|||||||
{
|
{
|
||||||
VideoFileExtensions = new[]
|
VideoFileExtensions = new[]
|
||||||
{
|
{
|
||||||
".m4v",
|
".001",
|
||||||
|
".3g2",
|
||||||
".3gp",
|
".3gp",
|
||||||
".nsv",
|
".amv",
|
||||||
".ts",
|
|
||||||
".ty",
|
|
||||||
".strm",
|
|
||||||
".rm",
|
|
||||||
".rmvb",
|
|
||||||
".ifo",
|
|
||||||
".mov",
|
|
||||||
".qt",
|
|
||||||
".divx",
|
|
||||||
".xvid",
|
|
||||||
".bivx",
|
|
||||||
".vob",
|
|
||||||
".nrg",
|
|
||||||
".img",
|
|
||||||
".iso",
|
|
||||||
".pva",
|
|
||||||
".wmv",
|
|
||||||
".asf",
|
".asf",
|
||||||
".asx",
|
".asx",
|
||||||
".ogm",
|
|
||||||
".m2v",
|
|
||||||
".avi",
|
".avi",
|
||||||
".bin",
|
".bin",
|
||||||
".dvr-ms",
|
".bivx",
|
||||||
".mpg",
|
".divx",
|
||||||
".mpeg",
|
|
||||||
".mp4",
|
|
||||||
".mkv",
|
|
||||||
".avc",
|
|
||||||
".vp3",
|
|
||||||
".svq3",
|
|
||||||
".nuv",
|
|
||||||
".viv",
|
|
||||||
".dv",
|
".dv",
|
||||||
|
".dvr-ms",
|
||||||
|
".f4v",
|
||||||
".fli",
|
".fli",
|
||||||
".flv",
|
".flv",
|
||||||
".001",
|
".ifo",
|
||||||
".tp"
|
".img",
|
||||||
|
".iso",
|
||||||
|
".m2t",
|
||||||
|
".m2ts",
|
||||||
|
".m2v",
|
||||||
|
".m4v",
|
||||||
|
".mkv",
|
||||||
|
".mk3d",
|
||||||
|
".mov",
|
||||||
|
".mp4",
|
||||||
|
".mpe",
|
||||||
|
".mpeg",
|
||||||
|
".mpg",
|
||||||
|
".mts",
|
||||||
|
".mxf",
|
||||||
|
".nrg",
|
||||||
|
".nsv",
|
||||||
|
".nuv",
|
||||||
|
".ogg",
|
||||||
|
".ogm",
|
||||||
|
".ogv",
|
||||||
|
".pva",
|
||||||
|
".qt",
|
||||||
|
".rec",
|
||||||
|
".rm",
|
||||||
|
".rmvb",
|
||||||
|
".strm",
|
||||||
|
".svq3",
|
||||||
|
".tp",
|
||||||
|
".ts",
|
||||||
|
".ty",
|
||||||
|
".viv",
|
||||||
|
".vob",
|
||||||
|
".vp3",
|
||||||
|
".webm",
|
||||||
|
".wmv",
|
||||||
|
".wtv",
|
||||||
|
".xvid"
|
||||||
};
|
};
|
||||||
|
|
||||||
VideoFlagDelimiters = new[]
|
VideoFlagDelimiters = new[]
|
||||||
@@ -124,11 +138,11 @@ namespace Emby.Naming.Common
|
|||||||
token: "DSR")
|
token: "DSR")
|
||||||
};
|
};
|
||||||
|
|
||||||
VideoFileStackingExpressions = new[]
|
VideoFileStackingRules = new[]
|
||||||
{
|
{
|
||||||
"(?<title>.*?)(?<volume>[ _.-]*(?:cd|dvd|p(?:ar)?t|dis[ck])[ _.-]*[0-9]+)(?<ignore>.*?)(?<extension>\\.[^.]+)$",
|
new FileStackRule(@"^(?<filename>.*?)(?:(?<=[\]\)\}])|[ _.-]+)[\(\[]?(?<parttype>cd|dvd|part|pt|dis[ck])[ _.-]*(?<number>[0-9]+)[\)\]]?(?:\.[^.]+)?$", true),
|
||||||
"(?<title>.*?)(?<volume>[ _.-]*(?:cd|dvd|p(?:ar)?t|dis[ck])[ _.-]*[a-d])(?<ignore>.*?)(?<extension>\\.[^.]+)$",
|
new FileStackRule(@"^(?<filename>.*?)(?:(?<=[\]\)\}])|[ _.-]+)[\(\[]?(?<parttype>cd|dvd|part|pt|dis[ck])[ _.-]*(?<number>[a-d])[\)\]]?(?:\.[^.]+)?$", false),
|
||||||
"(?<title>.*?)(?<volume>[ ._-]*[a-d])(?<ignore>.*?)(?<extension>\\.[^.]+)$"
|
new FileStackRule(@"^(?<filename>.*?)(?:(?<=[\]\)\}])|[ _.-]?)(?<number>[a-d])(?:\.[^.]+)?$", false)
|
||||||
};
|
};
|
||||||
|
|
||||||
CleanDateTimes = new[]
|
CleanDateTimes = new[]
|
||||||
@@ -148,32 +162,20 @@ namespace Emby.Naming.Common
|
|||||||
|
|
||||||
SubtitleFileExtensions = new[]
|
SubtitleFileExtensions = new[]
|
||||||
{
|
{
|
||||||
|
".ass",
|
||||||
|
".mks",
|
||||||
|
".sami",
|
||||||
|
".smi",
|
||||||
".srt",
|
".srt",
|
||||||
".ssa",
|
".ssa",
|
||||||
".ass",
|
".sub",
|
||||||
".sub"
|
".vtt",
|
||||||
};
|
|
||||||
|
|
||||||
SubtitleFlagDelimiters = new[]
|
|
||||||
{
|
|
||||||
'.'
|
|
||||||
};
|
|
||||||
|
|
||||||
SubtitleForcedFlags = new[]
|
|
||||||
{
|
|
||||||
"foreign",
|
|
||||||
"forced"
|
|
||||||
};
|
|
||||||
|
|
||||||
SubtitleDefaultFlags = new[]
|
|
||||||
{
|
|
||||||
"default"
|
|
||||||
};
|
};
|
||||||
|
|
||||||
AlbumStackingPrefixes = new[]
|
AlbumStackingPrefixes = new[]
|
||||||
{
|
{
|
||||||
"disc",
|
|
||||||
"cd",
|
"cd",
|
||||||
|
"disc",
|
||||||
"disk",
|
"disk",
|
||||||
"vol",
|
"vol",
|
||||||
"volume"
|
"volume"
|
||||||
@@ -181,68 +183,101 @@ namespace Emby.Naming.Common
|
|||||||
|
|
||||||
AudioFileExtensions = new[]
|
AudioFileExtensions = new[]
|
||||||
{
|
{
|
||||||
".nsv",
|
|
||||||
".m4a",
|
|
||||||
".flac",
|
|
||||||
".aac",
|
|
||||||
".strm",
|
|
||||||
".pls",
|
|
||||||
".rm",
|
|
||||||
".mpa",
|
|
||||||
".wav",
|
|
||||||
".wma",
|
|
||||||
".ogg",
|
|
||||||
".opus",
|
|
||||||
".mp3",
|
|
||||||
".mp2",
|
|
||||||
".mod",
|
|
||||||
".amf",
|
|
||||||
".669",
|
".669",
|
||||||
|
".3gp",
|
||||||
|
".aa",
|
||||||
|
".aac",
|
||||||
|
".aax",
|
||||||
|
".ac3",
|
||||||
|
".act",
|
||||||
|
".adp",
|
||||||
|
".adplug",
|
||||||
|
".adx",
|
||||||
|
".afc",
|
||||||
|
".amf",
|
||||||
|
".aif",
|
||||||
|
".aiff",
|
||||||
|
".alac",
|
||||||
|
".amr",
|
||||||
|
".ape",
|
||||||
|
".ast",
|
||||||
|
".au",
|
||||||
|
".awb",
|
||||||
|
".cda",
|
||||||
|
".cue",
|
||||||
".dmf",
|
".dmf",
|
||||||
|
".dsf",
|
||||||
".dsm",
|
".dsm",
|
||||||
|
".dsp",
|
||||||
|
".dts",
|
||||||
|
".dvf",
|
||||||
".far",
|
".far",
|
||||||
|
".flac",
|
||||||
".gdm",
|
".gdm",
|
||||||
|
".gsm",
|
||||||
|
".gym",
|
||||||
|
".hps",
|
||||||
".imf",
|
".imf",
|
||||||
".it",
|
".it",
|
||||||
".m15",
|
".m15",
|
||||||
|
".m4a",
|
||||||
|
".m4b",
|
||||||
|
".mac",
|
||||||
".med",
|
".med",
|
||||||
|
".mka",
|
||||||
|
".mmf",
|
||||||
|
".mod",
|
||||||
|
".mogg",
|
||||||
|
".mp2",
|
||||||
|
".mp3",
|
||||||
|
".mpa",
|
||||||
|
".mpc",
|
||||||
|
".mpp",
|
||||||
|
".mp+",
|
||||||
|
".msv",
|
||||||
|
".nmf",
|
||||||
|
".nsf",
|
||||||
|
".nsv",
|
||||||
|
".oga",
|
||||||
|
".ogg",
|
||||||
".okt",
|
".okt",
|
||||||
|
".opus",
|
||||||
|
".pls",
|
||||||
|
".ra",
|
||||||
|
".rf64",
|
||||||
|
".rm",
|
||||||
".s3m",
|
".s3m",
|
||||||
".stm",
|
|
||||||
".sfx",
|
".sfx",
|
||||||
|
".shn",
|
||||||
|
".sid",
|
||||||
|
".spc",
|
||||||
|
".stm",
|
||||||
|
".strm",
|
||||||
".ult",
|
".ult",
|
||||||
".uni",
|
".uni",
|
||||||
".xm",
|
".vox",
|
||||||
".sid",
|
".wav",
|
||||||
".ac3",
|
".wma",
|
||||||
".dts",
|
|
||||||
".cue",
|
|
||||||
".aif",
|
|
||||||
".aiff",
|
|
||||||
".ape",
|
|
||||||
".mac",
|
|
||||||
".mpc",
|
|
||||||
".mp+",
|
|
||||||
".mpp",
|
|
||||||
".shn",
|
|
||||||
".wv",
|
".wv",
|
||||||
".nsf",
|
".xm",
|
||||||
".spc",
|
|
||||||
".gym",
|
|
||||||
".adplug",
|
|
||||||
".adx",
|
|
||||||
".dsp",
|
|
||||||
".adp",
|
|
||||||
".ymf",
|
|
||||||
".ast",
|
|
||||||
".afc",
|
|
||||||
".hps",
|
|
||||||
".xsp",
|
".xsp",
|
||||||
".acc",
|
".ymf"
|
||||||
".m4b",
|
};
|
||||||
".oga",
|
|
||||||
".dsf",
|
MediaFlagDelimiters = new[]
|
||||||
".mka"
|
{
|
||||||
|
'.'
|
||||||
|
};
|
||||||
|
|
||||||
|
MediaForcedFlags = new[]
|
||||||
|
{
|
||||||
|
"foreign",
|
||||||
|
"forced"
|
||||||
|
};
|
||||||
|
|
||||||
|
MediaDefaultFlags = new[]
|
||||||
|
{
|
||||||
|
"default"
|
||||||
};
|
};
|
||||||
|
|
||||||
EpisodeExpressions = new[]
|
EpisodeExpressions = new[]
|
||||||
@@ -403,6 +438,72 @@ namespace Emby.Naming.Common
|
|||||||
|
|
||||||
VideoExtraRules = new[]
|
VideoExtraRules = new[]
|
||||||
{
|
{
|
||||||
|
new ExtraRule(
|
||||||
|
ExtraType.Trailer,
|
||||||
|
ExtraRuleType.DirectoryName,
|
||||||
|
"trailers",
|
||||||
|
MediaType.Video),
|
||||||
|
|
||||||
|
new ExtraRule(
|
||||||
|
ExtraType.ThemeVideo,
|
||||||
|
ExtraRuleType.DirectoryName,
|
||||||
|
"backdrops",
|
||||||
|
MediaType.Video),
|
||||||
|
|
||||||
|
new ExtraRule(
|
||||||
|
ExtraType.ThemeSong,
|
||||||
|
ExtraRuleType.DirectoryName,
|
||||||
|
"theme-music",
|
||||||
|
MediaType.Audio),
|
||||||
|
|
||||||
|
new ExtraRule(
|
||||||
|
ExtraType.BehindTheScenes,
|
||||||
|
ExtraRuleType.DirectoryName,
|
||||||
|
"behind the scenes",
|
||||||
|
MediaType.Video),
|
||||||
|
|
||||||
|
new ExtraRule(
|
||||||
|
ExtraType.DeletedScene,
|
||||||
|
ExtraRuleType.DirectoryName,
|
||||||
|
"deleted scenes",
|
||||||
|
MediaType.Video),
|
||||||
|
|
||||||
|
new ExtraRule(
|
||||||
|
ExtraType.Interview,
|
||||||
|
ExtraRuleType.DirectoryName,
|
||||||
|
"interviews",
|
||||||
|
MediaType.Video),
|
||||||
|
|
||||||
|
new ExtraRule(
|
||||||
|
ExtraType.Scene,
|
||||||
|
ExtraRuleType.DirectoryName,
|
||||||
|
"scenes",
|
||||||
|
MediaType.Video),
|
||||||
|
|
||||||
|
new ExtraRule(
|
||||||
|
ExtraType.Sample,
|
||||||
|
ExtraRuleType.DirectoryName,
|
||||||
|
"samples",
|
||||||
|
MediaType.Video),
|
||||||
|
|
||||||
|
new ExtraRule(
|
||||||
|
ExtraType.Clip,
|
||||||
|
ExtraRuleType.DirectoryName,
|
||||||
|
"shorts",
|
||||||
|
MediaType.Video),
|
||||||
|
|
||||||
|
new ExtraRule(
|
||||||
|
ExtraType.Clip,
|
||||||
|
ExtraRuleType.DirectoryName,
|
||||||
|
"featurettes",
|
||||||
|
MediaType.Video),
|
||||||
|
|
||||||
|
new ExtraRule(
|
||||||
|
ExtraType.Unknown,
|
||||||
|
ExtraRuleType.DirectoryName,
|
||||||
|
"extras",
|
||||||
|
MediaType.Video),
|
||||||
|
|
||||||
new ExtraRule(
|
new ExtraRule(
|
||||||
ExtraType.Trailer,
|
ExtraType.Trailer,
|
||||||
ExtraRuleType.Filename,
|
ExtraRuleType.Filename,
|
||||||
@@ -517,55 +618,17 @@ namespace Emby.Naming.Common
|
|||||||
"-short",
|
"-short",
|
||||||
MediaType.Video),
|
MediaType.Video),
|
||||||
|
|
||||||
new ExtraRule(
|
|
||||||
ExtraType.BehindTheScenes,
|
|
||||||
ExtraRuleType.DirectoryName,
|
|
||||||
"behind the scenes",
|
|
||||||
MediaType.Video),
|
|
||||||
|
|
||||||
new ExtraRule(
|
|
||||||
ExtraType.DeletedScene,
|
|
||||||
ExtraRuleType.DirectoryName,
|
|
||||||
"deleted scenes",
|
|
||||||
MediaType.Video),
|
|
||||||
|
|
||||||
new ExtraRule(
|
|
||||||
ExtraType.Interview,
|
|
||||||
ExtraRuleType.DirectoryName,
|
|
||||||
"interviews",
|
|
||||||
MediaType.Video),
|
|
||||||
|
|
||||||
new ExtraRule(
|
|
||||||
ExtraType.Scene,
|
|
||||||
ExtraRuleType.DirectoryName,
|
|
||||||
"scenes",
|
|
||||||
MediaType.Video),
|
|
||||||
|
|
||||||
new ExtraRule(
|
|
||||||
ExtraType.Sample,
|
|
||||||
ExtraRuleType.DirectoryName,
|
|
||||||
"samples",
|
|
||||||
MediaType.Video),
|
|
||||||
|
|
||||||
new ExtraRule(
|
|
||||||
ExtraType.Clip,
|
|
||||||
ExtraRuleType.DirectoryName,
|
|
||||||
"shorts",
|
|
||||||
MediaType.Video),
|
|
||||||
|
|
||||||
new ExtraRule(
|
|
||||||
ExtraType.Clip,
|
|
||||||
ExtraRuleType.DirectoryName,
|
|
||||||
"featurettes",
|
|
||||||
MediaType.Video),
|
|
||||||
|
|
||||||
new ExtraRule(
|
new ExtraRule(
|
||||||
ExtraType.Unknown,
|
ExtraType.Unknown,
|
||||||
ExtraRuleType.DirectoryName,
|
ExtraRuleType.Suffix,
|
||||||
"extras",
|
"-extra",
|
||||||
MediaType.Video),
|
MediaType.Video)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
AllExtrasTypesFolderNames = VideoExtraRules
|
||||||
|
.Where(i => i.RuleType == ExtraRuleType.DirectoryName)
|
||||||
|
.ToDictionary(i => i.Token, i => i.ExtraType, StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
Format3DRules = new[]
|
Format3DRules = new[]
|
||||||
{
|
{
|
||||||
// Kodi rules:
|
// Kodi rules:
|
||||||
@@ -619,41 +682,6 @@ namespace Emby.Naming.Common
|
|||||||
@"^\s*(?<name>[^ ].*?)\s*$"
|
@"^\s*(?<name>[^ ].*?)\s*$"
|
||||||
};
|
};
|
||||||
|
|
||||||
var extensions = VideoFileExtensions.ToList();
|
|
||||||
|
|
||||||
extensions.AddRange(new[]
|
|
||||||
{
|
|
||||||
".mkv",
|
|
||||||
".m2t",
|
|
||||||
".m2ts",
|
|
||||||
".img",
|
|
||||||
".iso",
|
|
||||||
".mk3d",
|
|
||||||
".ts",
|
|
||||||
".rmvb",
|
|
||||||
".mov",
|
|
||||||
".avi",
|
|
||||||
".mpg",
|
|
||||||
".mpeg",
|
|
||||||
".wmv",
|
|
||||||
".mp4",
|
|
||||||
".divx",
|
|
||||||
".dvr-ms",
|
|
||||||
".wtv",
|
|
||||||
".ogm",
|
|
||||||
".ogv",
|
|
||||||
".asf",
|
|
||||||
".m4v",
|
|
||||||
".flv",
|
|
||||||
".f4v",
|
|
||||||
".3gp",
|
|
||||||
".webm",
|
|
||||||
".mts",
|
|
||||||
".m2v",
|
|
||||||
".rec",
|
|
||||||
".mxf"
|
|
||||||
});
|
|
||||||
|
|
||||||
MultipleEpisodeExpressions = new[]
|
MultipleEpisodeExpressions = new[]
|
||||||
{
|
{
|
||||||
@".*(\\|\/)[sS]?(?<seasonnumber>[0-9]{1,4})[xX](?<epnumber>[0-9]{1,3})((-| - )[0-9]{1,4}[eExX](?<endingepnumber>[0-9]{1,3}))+[^\\\/]*$",
|
@".*(\\|\/)[sS]?(?<seasonnumber>[0-9]{1,4})[xX](?<epnumber>[0-9]{1,3})((-| - )[0-9]{1,4}[eExX](?<endingepnumber>[0-9]{1,3}))+[^\\\/]*$",
|
||||||
@@ -671,18 +699,34 @@ namespace Emby.Naming.Common
|
|||||||
IsNamed = true
|
IsNamed = true
|
||||||
}).ToArray();
|
}).ToArray();
|
||||||
|
|
||||||
VideoFileExtensions = extensions
|
|
||||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
Compile();
|
Compile();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the folder name to extra types mapping.
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<string, ExtraType> AllExtrasTypesFolderNames { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets list of audio file extensions.
|
/// Gets or sets list of audio file extensions.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string[] AudioFileExtensions { get; set; }
|
public string[] AudioFileExtensions { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets list of external media flag delimiters.
|
||||||
|
/// </summary>
|
||||||
|
public char[] MediaFlagDelimiters { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets list of external media forced flags.
|
||||||
|
/// </summary>
|
||||||
|
public string[] MediaForcedFlags { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets list of external media default flags.
|
||||||
|
/// </summary>
|
||||||
|
public string[] MediaDefaultFlags { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets list of album stacking prefixes.
|
/// Gets or sets list of album stacking prefixes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -693,21 +737,6 @@ namespace Emby.Naming.Common
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string[] SubtitleFileExtensions { get; set; }
|
public string[] SubtitleFileExtensions { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets list of subtitles flag delimiters.
|
|
||||||
/// </summary>
|
|
||||||
public char[] SubtitleFlagDelimiters { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets list of subtitle forced flags.
|
|
||||||
/// </summary>
|
|
||||||
public string[] SubtitleForcedFlags { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets list of subtitle default flags.
|
|
||||||
/// </summary>
|
|
||||||
public string[] SubtitleDefaultFlags { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets list of episode regular expressions.
|
/// Gets or sets list of episode regular expressions.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -759,9 +788,9 @@ namespace Emby.Naming.Common
|
|||||||
public Format3DRule[] Format3DRules { get; set; }
|
public Format3DRule[] Format3DRules { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets list of raw video file-stacking expressions strings.
|
/// Gets the file stacking rules.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string[] VideoFileStackingExpressions { get; set; }
|
public FileStackRule[] VideoFileStackingRules { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets list of raw clean DateTimes regular expressions strings.
|
/// Gets or sets list of raw clean DateTimes regular expressions strings.
|
||||||
@@ -783,11 +812,6 @@ namespace Emby.Naming.Common
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public ExtraRule[] VideoExtraRules { get; set; }
|
public ExtraRule[] VideoExtraRules { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets list of video file-stack regular expressions.
|
|
||||||
/// </summary>
|
|
||||||
public Regex[] VideoFileStackingRegexes { get; private set; } = Array.Empty<Regex>();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets list of clean datetime regular expressions.
|
/// Gets list of clean datetime regular expressions.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -813,7 +837,6 @@ namespace Emby.Naming.Common
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void Compile()
|
public void Compile()
|
||||||
{
|
{
|
||||||
VideoFileStackingRegexes = VideoFileStackingExpressions.Select(Compile).ToArray();
|
|
||||||
CleanDateTimeRegexes = CleanDateTimes.Select(Compile).ToArray();
|
CleanDateTimeRegexes = CleanDateTimes.Select(Compile).ToArray();
|
||||||
CleanStringRegexes = CleanStrings.Select(Compile).ToArray();
|
CleanStringRegexes = CleanStrings.Select(Compile).ToArray();
|
||||||
EpisodeWithoutSeasonRegexes = EpisodeWithoutSeasonExpressions.Select(Compile).ToArray();
|
EpisodeWithoutSeasonRegexes = EpisodeWithoutSeasonExpressions.Select(Compile).ToArray();
|
||||||
|
|||||||
@@ -15,6 +15,10 @@
|
|||||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||||
|
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition=" '$(Stability)'=='Unstable'">
|
<PropertyGroup Condition=" '$(Stability)'=='Unstable'">
|
||||||
<!-- Include all symbols in the main nupkg until Azure Artifact Feed starts supporting ingesting NuGet symbol packages. -->
|
<!-- Include all symbols in the main nupkg until Azure Artifact Feed starts supporting ingesting NuGet symbol packages. -->
|
||||||
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
|
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
|
||||||
@@ -38,13 +42,17 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.0" PrivateAssets="All" />
|
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<!-- Code Analyzers-->
|
<!-- Code Analyzers-->
|
||||||
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||||
|
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.3">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
|
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
|
||||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
|
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.406" PrivateAssets="All" />
|
||||||
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
|
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
116
Emby.Naming/ExternalFiles/ExternalPathParser.cs
Normal file
116
Emby.Naming/ExternalFiles/ExternalPathParser.cs
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using Emby.Naming.Common;
|
||||||
|
using Jellyfin.Extensions;
|
||||||
|
using MediaBrowser.Model.Dlna;
|
||||||
|
using MediaBrowser.Model.Globalization;
|
||||||
|
|
||||||
|
namespace Emby.Naming.ExternalFiles
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// External media file parser class.
|
||||||
|
/// </summary>
|
||||||
|
public class ExternalPathParser
|
||||||
|
{
|
||||||
|
private readonly NamingOptions _namingOptions;
|
||||||
|
private readonly DlnaProfileType _type;
|
||||||
|
private readonly ILocalizationManager _localizationManager;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ExternalPathParser"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="localizationManager">The localization manager.</param>
|
||||||
|
/// <param name="namingOptions">The <see cref="NamingOptions"/> object containing FileExtensions, MediaDefaultFlags, MediaForcedFlags and MediaFlagDelimiters.</param>
|
||||||
|
/// <param name="type">The <see cref="DlnaProfileType"/> of the parsed file.</param>
|
||||||
|
public ExternalPathParser(NamingOptions namingOptions, ILocalizationManager localizationManager, DlnaProfileType type)
|
||||||
|
{
|
||||||
|
_localizationManager = localizationManager;
|
||||||
|
_namingOptions = namingOptions;
|
||||||
|
_type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parse filename and extract information.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">Path to file.</param>
|
||||||
|
/// <param name="extraString">Part of the filename only containing the extra information.</param>
|
||||||
|
/// <returns>Returns null or an <see cref="ExternalPathParserResult"/> object if parsing is successful.</returns>
|
||||||
|
public ExternalPathParserResult? ParseFile(string path, string? extraString)
|
||||||
|
{
|
||||||
|
if (path.Length == 0)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var extension = Path.GetExtension(path);
|
||||||
|
if (!(_type == DlnaProfileType.Subtitle && _namingOptions.SubtitleFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase))
|
||||||
|
&& !(_type == DlnaProfileType.Audio && _namingOptions.AudioFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase)))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var pathInfo = new ExternalPathParserResult(path);
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(extraString))
|
||||||
|
{
|
||||||
|
return pathInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var separator in _namingOptions.MediaFlagDelimiters)
|
||||||
|
{
|
||||||
|
var languageString = extraString;
|
||||||
|
var titleString = string.Empty;
|
||||||
|
const int SeparatorLength = 1;
|
||||||
|
|
||||||
|
while (languageString.Length > 0)
|
||||||
|
{
|
||||||
|
int lastSeparator = languageString.LastIndexOf(separator);
|
||||||
|
|
||||||
|
if (lastSeparator == -1)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
string currentSlice = languageString[lastSeparator..];
|
||||||
|
string currentSliceWithoutSeparator = currentSlice[SeparatorLength..];
|
||||||
|
|
||||||
|
if (_namingOptions.MediaDefaultFlags.Any(s => currentSliceWithoutSeparator.Contains(s, StringComparison.OrdinalIgnoreCase)))
|
||||||
|
{
|
||||||
|
pathInfo.IsDefault = true;
|
||||||
|
extraString = extraString.Replace(currentSlice, string.Empty, StringComparison.OrdinalIgnoreCase);
|
||||||
|
languageString = languageString[..lastSeparator];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_namingOptions.MediaForcedFlags.Any(s => currentSliceWithoutSeparator.Contains(s, StringComparison.OrdinalIgnoreCase)))
|
||||||
|
{
|
||||||
|
pathInfo.IsForced = true;
|
||||||
|
extraString = extraString.Replace(currentSlice, string.Empty, StringComparison.OrdinalIgnoreCase);
|
||||||
|
languageString = languageString[..lastSeparator];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to translate to three character code
|
||||||
|
var culture = _localizationManager.FindLanguageInfo(currentSliceWithoutSeparator);
|
||||||
|
|
||||||
|
if (culture != null && pathInfo.Language == null)
|
||||||
|
{
|
||||||
|
pathInfo.Language = culture.ThreeLetterISOLanguageName;
|
||||||
|
extraString = extraString.Replace(currentSlice, string.Empty, StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
titleString = currentSlice + titleString;
|
||||||
|
}
|
||||||
|
|
||||||
|
languageString = languageString[..lastSeparator];
|
||||||
|
}
|
||||||
|
|
||||||
|
pathInfo.Title = titleString.Length >= SeparatorLength ? titleString[SeparatorLength..] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return pathInfo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,17 +1,17 @@
|
|||||||
namespace Emby.Naming.Subtitles
|
namespace Emby.Naming.ExternalFiles
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class holding information about subtitle.
|
/// Class holding information about external files.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class SubtitleInfo
|
public class ExternalPathParserResult
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="SubtitleInfo"/> class.
|
/// Initializes a new instance of the <see cref="ExternalPathParserResult"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="path">Path to file.</param>
|
/// <param name="path">Path to file.</param>
|
||||||
/// <param name="isDefault">Is subtitle default.</param>
|
/// <param name="isDefault">Is default.</param>
|
||||||
/// <param name="isForced">Is subtitle forced.</param>
|
/// <param name="isForced">Is forced.</param>
|
||||||
public SubtitleInfo(string path, bool isDefault, bool isForced)
|
public ExternalPathParserResult(string path, bool isDefault = false, bool isForced = false)
|
||||||
{
|
{
|
||||||
Path = path;
|
Path = path;
|
||||||
IsDefault = isDefault;
|
IsDefault = isDefault;
|
||||||
@@ -30,6 +30,12 @@ namespace Emby.Naming.Subtitles
|
|||||||
/// <value>The language.</value>
|
/// <value>The language.</value>
|
||||||
public string? Language { get; set; }
|
public string? Language { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the title.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The title.</value>
|
||||||
|
public string? Title { 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>
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using Emby.Naming.Common;
|
|
||||||
|
|
||||||
namespace Emby.Naming.Subtitles
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Subtitle Parser class.
|
|
||||||
/// </summary>
|
|
||||||
public class SubtitleParser
|
|
||||||
{
|
|
||||||
private readonly NamingOptions _options;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="SubtitleParser"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="options"><see cref="NamingOptions"/> object containing SubtitleFileExtensions, SubtitleDefaultFlags, SubtitleForcedFlags and SubtitleFlagDelimiters.</param>
|
|
||||||
public SubtitleParser(NamingOptions options)
|
|
||||||
{
|
|
||||||
_options = options;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Parse file to determine if is subtitle and <see cref="SubtitleInfo"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="path">Path to file.</param>
|
|
||||||
/// <returns>Returns null or <see cref="SubtitleInfo"/> object if parsing is successful.</returns>
|
|
||||||
public SubtitleInfo? ParseFile(string path)
|
|
||||||
{
|
|
||||||
if (path.Length == 0)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var extension = Path.GetExtension(path);
|
|
||||||
if (!_options.SubtitleFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var flags = GetFlags(path);
|
|
||||||
var info = new SubtitleInfo(
|
|
||||||
path,
|
|
||||||
_options.SubtitleDefaultFlags.Any(i => flags.Contains(i, StringComparer.OrdinalIgnoreCase)),
|
|
||||||
_options.SubtitleForcedFlags.Any(i => flags.Contains(i, StringComparer.OrdinalIgnoreCase)));
|
|
||||||
|
|
||||||
var parts = flags.Where(i => !_options.SubtitleDefaultFlags.Contains(i, StringComparer.OrdinalIgnoreCase)
|
|
||||||
&& !_options.SubtitleForcedFlags.Contains(i, StringComparer.OrdinalIgnoreCase))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
// Should have a name, language and file extension
|
|
||||||
if (parts.Count >= 3)
|
|
||||||
{
|
|
||||||
info.Language = parts[^2];
|
|
||||||
}
|
|
||||||
|
|
||||||
return info;
|
|
||||||
}
|
|
||||||
|
|
||||||
private string[] GetFlags(string path)
|
|
||||||
{
|
|
||||||
// Note: the tags need be surrounded be either a space ( ), hyphen -, dot . or underscore _.
|
|
||||||
|
|
||||||
var file = Path.GetFileName(path);
|
|
||||||
|
|
||||||
return file.Split(_options.SubtitleFlagDelimiters, StringSplitOptions.RemoveEmptyEntries);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
|
||||||
using Emby.Naming.Common;
|
using Emby.Naming.Common;
|
||||||
using Emby.Naming.Video;
|
using Emby.Naming.Video;
|
||||||
|
using Jellyfin.Extensions;
|
||||||
|
|
||||||
namespace Emby.Naming.TV
|
namespace Emby.Naming.TV
|
||||||
{
|
{
|
||||||
@@ -48,7 +48,7 @@ namespace Emby.Naming.TV
|
|||||||
{
|
{
|
||||||
var extension = Path.GetExtension(path);
|
var extension = Path.GetExtension(path);
|
||||||
// Check supported extensions
|
// Check supported extensions
|
||||||
if (!_options.VideoFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
|
if (!_options.VideoFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
// It's not supported. Check stub extensions
|
// It's not supported. Check stub extensions
|
||||||
if (!StubResolver.TryResolveFile(path, _options, out stubType))
|
if (!StubResolver.TryResolveFile(path, _options, out stubType))
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ 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 static (int? seasonNumber, bool isSeasonFolder) GetSeasonNumberFromPath(
|
private static (int? SeasonNumber, bool IsSeasonFolder) GetSeasonNumberFromPath(
|
||||||
string path,
|
string path,
|
||||||
bool supportSpecialAliases,
|
bool supportSpecialAliases,
|
||||||
bool supportNumericSeasonFolders)
|
bool supportNumericSeasonFolders)
|
||||||
@@ -99,7 +99,7 @@ namespace Emby.Naming.TV
|
|||||||
if (filename.Contains(name, StringComparison.OrdinalIgnoreCase))
|
if (filename.Contains(name, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
var result = GetSeasonNumberFromPathSubstring(filename.Replace(name, " ", StringComparison.OrdinalIgnoreCase));
|
var result = GetSeasonNumberFromPathSubstring(filename.Replace(name, " ", StringComparison.OrdinalIgnoreCase));
|
||||||
if (result.seasonNumber.HasValue)
|
if (result.SeasonNumber.HasValue)
|
||||||
{
|
{
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -142,7 +142,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 static (int? seasonNumber, bool isSeasonFolder) GetSeasonNumberFromPathSubstring(ReadOnlySpan<char> path)
|
private static (int? SeasonNumber, bool IsSeasonFolder) GetSeasonNumberFromPathSubstring(ReadOnlySpan<char> path)
|
||||||
{
|
{
|
||||||
var numericStart = -1;
|
var numericStart = -1;
|
||||||
var length = 0;
|
var length = 0;
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
using System.Globalization;
|
|
||||||
using Emby.Naming.Common;
|
using Emby.Naming.Common;
|
||||||
|
|
||||||
namespace Emby.Naming.TV
|
namespace Emby.Naming.TV
|
||||||
@@ -51,7 +50,7 @@ namespace Emby.Naming.TV
|
|||||||
if (expression.IsNamed)
|
if (expression.IsNamed)
|
||||||
{
|
{
|
||||||
result.SeriesName = match.Groups["seriesname"].Value;
|
result.SeriesName = match.Groups["seriesname"].Value;
|
||||||
result.Success = !string.IsNullOrEmpty(result.SeriesName) && !string.IsNullOrEmpty(match.Groups["seasonnumber"]?.Value);
|
result.Success = !string.IsNullOrEmpty(result.SeriesName) && !match.Groups["seasonnumber"].ValueSpan.IsEmpty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|||||||
@@ -9,45 +9,27 @@ namespace Emby.Naming.Video
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Resolve if file is extra for video.
|
/// Resolve if file is extra for video.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ExtraResolver
|
public static class ExtraRuleResolver
|
||||||
{
|
{
|
||||||
private static readonly char[] _digits = new[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };
|
private static readonly char[] _digits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };
|
||||||
private readonly NamingOptions _options;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="ExtraResolver"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="options"><see cref="NamingOptions"/> object containing VideoExtraRules and passed to <see cref="AudioFileParser"/> and <see cref="VideoResolver"/>.</param>
|
|
||||||
public ExtraResolver(NamingOptions options)
|
|
||||||
{
|
|
||||||
_options = options;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Attempts to resolve if file is extra.
|
/// Attempts to resolve if file is extra.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="path">Path to file.</param>
|
/// <param name="path">Path to file.</param>
|
||||||
|
/// <param name="namingOptions">The naming options.</param>
|
||||||
/// <returns>Returns <see cref="ExtraResult"/> object.</returns>
|
/// <returns>Returns <see cref="ExtraResult"/> object.</returns>
|
||||||
public ExtraResult GetExtraInfo(string path)
|
public static ExtraResult GetExtraInfo(string path, NamingOptions namingOptions)
|
||||||
{
|
{
|
||||||
var result = new ExtraResult();
|
var result = new ExtraResult();
|
||||||
|
|
||||||
for (var i = 0; i < _options.VideoExtraRules.Length; i++)
|
for (var i = 0; i < namingOptions.VideoExtraRules.Length; i++)
|
||||||
{
|
{
|
||||||
var rule = _options.VideoExtraRules[i];
|
var rule = namingOptions.VideoExtraRules[i];
|
||||||
if (rule.MediaType == MediaType.Audio)
|
if ((rule.MediaType == MediaType.Audio && !AudioFileParser.IsAudioFile(path, namingOptions))
|
||||||
|
|| (rule.MediaType == MediaType.Video && !VideoResolver.IsVideoFile(path, namingOptions)))
|
||||||
{
|
{
|
||||||
if (!AudioFileParser.IsAudioFile(path, _options))
|
continue;
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (rule.MediaType == MediaType.Video)
|
|
||||||
{
|
|
||||||
if (!VideoResolver.IsVideoFile(path, _options))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var pathSpan = path.AsSpan();
|
var pathSpan = path.AsSpan();
|
||||||
@@ -76,9 +58,9 @@ namespace Emby.Naming.Video
|
|||||||
{
|
{
|
||||||
var filename = Path.GetFileName(path);
|
var filename = Path.GetFileName(path);
|
||||||
|
|
||||||
var regex = new Regex(rule.Token, RegexOptions.IgnoreCase);
|
var isMatch = Regex.IsMatch(filename, rule.Token, RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||||
|
|
||||||
if (regex.IsMatch(filename))
|
if (isMatch)
|
||||||
{
|
{
|
||||||
result.ExtraType = rule.ExtraType;
|
result.ExtraType = rule.ExtraType;
|
||||||
result.Rule = rule;
|
result.Rule = rule;
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using Jellyfin.Extensions;
|
||||||
|
|
||||||
namespace Emby.Naming.Video
|
namespace Emby.Naming.Video
|
||||||
{
|
{
|
||||||
@@ -12,25 +12,30 @@ namespace Emby.Naming.Video
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="FileStack"/> class.
|
/// Initializes a new instance of the <see cref="FileStack"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public FileStack()
|
/// <param name="name">The stack name.</param>
|
||||||
|
/// <param name="isDirectory">Whether the stack files are directories.</param>
|
||||||
|
/// <param name="files">The stack files.</param>
|
||||||
|
public FileStack(string name, bool isDirectory, IReadOnlyList<string> files)
|
||||||
{
|
{
|
||||||
Files = new List<string>();
|
Name = name;
|
||||||
|
IsDirectoryStack = isDirectory;
|
||||||
|
Files = files;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets name of file stack.
|
/// Gets the name of file stack.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Name { get; set; } = string.Empty;
|
public string Name { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets list of paths in stack.
|
/// Gets the list of paths in stack.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public List<string> Files { get; set; }
|
public IReadOnlyList<string> Files { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether stack is directory stack.
|
/// Gets a value indicating whether stack is directory stack.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsDirectoryStack { get; set; }
|
public bool IsDirectoryStack { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Helper function to determine if path is in the stack.
|
/// Helper function to determine if path is in the stack.
|
||||||
@@ -40,12 +45,12 @@ namespace Emby.Naming.Video
|
|||||||
/// <returns>True if file is in the stack.</returns>
|
/// <returns>True if file is in the stack.</returns>
|
||||||
public bool ContainsFile(string file, bool isDirectory)
|
public bool ContainsFile(string file, bool isDirectory)
|
||||||
{
|
{
|
||||||
if (IsDirectoryStack == isDirectory)
|
if (string.IsNullOrEmpty(file))
|
||||||
{
|
{
|
||||||
return Files.Contains(file, StringComparer.OrdinalIgnoreCase);
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return IsDirectoryStack == isDirectory && Files.Contains(file, StringComparison.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
48
Emby.Naming/Video/FileStackRule.cs
Normal file
48
Emby.Naming/Video/FileStackRule.cs
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
namespace Emby.Naming.Video;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Regex based rule for file stacking (eg. disc1, disc2).
|
||||||
|
/// </summary>
|
||||||
|
public class FileStackRule
|
||||||
|
{
|
||||||
|
private readonly Regex _tokenRegex;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="FileStackRule"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="token">Token.</param>
|
||||||
|
/// <param name="isNumerical">Whether the file stack rule uses numerical or alphabetical numbering.</param>
|
||||||
|
public FileStackRule(string token, bool isNumerical)
|
||||||
|
{
|
||||||
|
_tokenRegex = new Regex(token, RegexOptions.IgnoreCase);
|
||||||
|
IsNumerical = isNumerical;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether the rule uses numerical or alphabetical numbering.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsNumerical { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Match the input against the rule regex.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input">The input.</param>
|
||||||
|
/// <param name="result">The part type and number or <c>null</c>.</param>
|
||||||
|
/// <returns>A value indicating whether the input matched the rule.</returns>
|
||||||
|
public bool Match(string input, [NotNullWhen(true)] out (string StackName, string PartType, string PartNumber)? result)
|
||||||
|
{
|
||||||
|
result = null;
|
||||||
|
var match = _tokenRegex.Match(input);
|
||||||
|
if (!match.Success)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var partType = match.Groups["parttype"].Success ? match.Groups["parttype"].Value : "unknown";
|
||||||
|
result = (match.Groups["filename"].Value, partType, match.Groups["number"].Value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,7 +9,7 @@ namespace Emby.Naming.Video
|
|||||||
public static class Format3DParser
|
public static class Format3DParser
|
||||||
{
|
{
|
||||||
// Static default result to save on allocation costs.
|
// Static default result to save on allocation costs.
|
||||||
private static readonly Format3DResult _defaultResult = new (false, null);
|
private static readonly Format3DResult _defaultResult = new(false, null);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Parse 3D format related flags.
|
/// Parse 3D format related flags.
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using Emby.Naming.AudioBook;
|
using Emby.Naming.AudioBook;
|
||||||
using Emby.Naming.Common;
|
using Emby.Naming.Common;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
@@ -12,37 +11,28 @@ namespace Emby.Naming.Video
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Resolve <see cref="FileStack"/> from list of paths.
|
/// Resolve <see cref="FileStack"/> from list of paths.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class StackResolver
|
public static class StackResolver
|
||||||
{
|
{
|
||||||
private readonly NamingOptions _options;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="StackResolver"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="options"><see cref="NamingOptions"/> object containing VideoFileStackingRegexes and passes options to <see cref="VideoResolver"/>.</param>
|
|
||||||
public StackResolver(NamingOptions options)
|
|
||||||
{
|
|
||||||
_options = options;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Resolves only directories from paths.
|
/// Resolves only directories from paths.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="files">List of paths.</param>
|
/// <param name="files">List of paths.</param>
|
||||||
|
/// <param name="namingOptions">The naming options.</param>
|
||||||
/// <returns>Enumerable <see cref="FileStack"/> of directories.</returns>
|
/// <returns>Enumerable <see cref="FileStack"/> of directories.</returns>
|
||||||
public IEnumerable<FileStack> ResolveDirectories(IEnumerable<string> files)
|
public static IEnumerable<FileStack> ResolveDirectories(IEnumerable<string> files, NamingOptions namingOptions)
|
||||||
{
|
{
|
||||||
return Resolve(files.Select(i => new FileSystemMetadata { FullName = i, IsDirectory = true }));
|
return Resolve(files.Select(i => new FileSystemMetadata { FullName = i, IsDirectory = true }), namingOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Resolves only files from paths.
|
/// Resolves only files from paths.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="files">List of paths.</param>
|
/// <param name="files">List of paths.</param>
|
||||||
|
/// <param name="namingOptions">The naming options.</param>
|
||||||
/// <returns>Enumerable <see cref="FileStack"/> of files.</returns>
|
/// <returns>Enumerable <see cref="FileStack"/> of files.</returns>
|
||||||
public IEnumerable<FileStack> ResolveFiles(IEnumerable<string> files)
|
public static IEnumerable<FileStack> ResolveFiles(IEnumerable<string> files, NamingOptions namingOptions)
|
||||||
{
|
{
|
||||||
return Resolve(files.Select(i => new FileSystemMetadata { FullName = i, IsDirectory = false }));
|
return Resolve(files.Select(i => new FileSystemMetadata { FullName = i, IsDirectory = false }), namingOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -50,7 +40,7 @@ namespace Emby.Naming.Video
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="files">List of paths.</param>
|
/// <param name="files">List of paths.</param>
|
||||||
/// <returns>Enumerable <see cref="FileStack"/> of directories.</returns>
|
/// <returns>Enumerable <see cref="FileStack"/> of directories.</returns>
|
||||||
public IEnumerable<FileStack> ResolveAudioBooks(IEnumerable<AudioBookFileInfo> files)
|
public static IEnumerable<FileStack> ResolveAudioBooks(IEnumerable<AudioBookFileInfo> files)
|
||||||
{
|
{
|
||||||
var groupedDirectoryFiles = files.GroupBy(file => Path.GetDirectoryName(file.Path));
|
var groupedDirectoryFiles = files.GroupBy(file => Path.GetDirectoryName(file.Path));
|
||||||
|
|
||||||
@@ -60,19 +50,13 @@ namespace Emby.Naming.Video
|
|||||||
{
|
{
|
||||||
foreach (var file in directory)
|
foreach (var file in directory)
|
||||||
{
|
{
|
||||||
var stack = new FileStack { Name = Path.GetFileNameWithoutExtension(file.Path), IsDirectoryStack = false };
|
var stack = new FileStack(Path.GetFileNameWithoutExtension(file.Path), false, new[] { file.Path });
|
||||||
stack.Files.Add(file.Path);
|
|
||||||
yield return stack;
|
yield return stack;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var stack = new FileStack { Name = Path.GetFileName(directory.Key), IsDirectoryStack = false };
|
var stack = new FileStack(Path.GetFileName(directory.Key), false, directory.Select(f => f.Path).ToArray());
|
||||||
foreach (var file in directory)
|
|
||||||
{
|
|
||||||
stack.Files.Add(file.Path);
|
|
||||||
}
|
|
||||||
|
|
||||||
yield return stack;
|
yield return stack;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -82,158 +66,91 @@ namespace Emby.Naming.Video
|
|||||||
/// Resolves videos from paths.
|
/// Resolves videos from paths.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="files">List of paths.</param>
|
/// <param name="files">List of paths.</param>
|
||||||
|
/// <param name="namingOptions">The naming options.</param>
|
||||||
/// <returns>Enumerable <see cref="FileStack"/> of videos.</returns>
|
/// <returns>Enumerable <see cref="FileStack"/> of videos.</returns>
|
||||||
public IEnumerable<FileStack> Resolve(IEnumerable<FileSystemMetadata> files)
|
public static IEnumerable<FileStack> Resolve(IEnumerable<FileSystemMetadata> files, NamingOptions namingOptions)
|
||||||
{
|
{
|
||||||
var list = files
|
var potentialFiles = files
|
||||||
.Where(i => i.IsDirectory || VideoResolver.IsVideoFile(i.FullName, _options) || VideoResolver.IsStubFile(i.FullName, _options))
|
.Where(i => i.IsDirectory || VideoResolver.IsVideoFile(i.FullName, namingOptions) || VideoResolver.IsStubFile(i.FullName, namingOptions))
|
||||||
.OrderBy(i => i.FullName)
|
.OrderBy(i => i.FullName);
|
||||||
.ToList();
|
|
||||||
|
|
||||||
var expressions = _options.VideoFileStackingRegexes;
|
var potentialStacks = new Dictionary<string, StackMetadata>();
|
||||||
|
foreach (var file in potentialFiles)
|
||||||
for (var i = 0; i < list.Count; i++)
|
|
||||||
{
|
{
|
||||||
var offset = 0;
|
var name = file.Name;
|
||||||
|
if (string.IsNullOrEmpty(name))
|
||||||
var file1 = list[i];
|
|
||||||
|
|
||||||
var expressionIndex = 0;
|
|
||||||
while (expressionIndex < expressions.Length)
|
|
||||||
{
|
{
|
||||||
var exp = expressions[expressionIndex];
|
name = Path.GetFileName(file.FullName);
|
||||||
var stack = new FileStack();
|
}
|
||||||
|
|
||||||
// (Title)(Volume)(Ignore)(Extension)
|
for (var i = 0; i < namingOptions.VideoFileStackingRules.Length; i++)
|
||||||
var match1 = FindMatch(file1, exp, offset);
|
{
|
||||||
|
var rule = namingOptions.VideoFileStackingRules[i];
|
||||||
if (match1.Success)
|
if (!rule.Match(name, out var stackParsingResult))
|
||||||
{
|
{
|
||||||
var title1 = match1.Groups["title"].Value;
|
continue;
|
||||||
var volume1 = match1.Groups["volume"].Value;
|
}
|
||||||
var ignore1 = match1.Groups["ignore"].Value;
|
|
||||||
var extension1 = match1.Groups["extension"].Value;
|
|
||||||
|
|
||||||
var j = i + 1;
|
var stackName = stackParsingResult.Value.StackName;
|
||||||
while (j < list.Count)
|
var partNumber = stackParsingResult.Value.PartNumber;
|
||||||
|
var partType = stackParsingResult.Value.PartType;
|
||||||
|
|
||||||
|
if (!potentialStacks.TryGetValue(stackName, out var stackResult))
|
||||||
|
{
|
||||||
|
stackResult = new StackMetadata(file.IsDirectory, rule.IsNumerical, partType);
|
||||||
|
potentialStacks[stackName] = stackResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stackResult.Parts.Count > 0)
|
||||||
|
{
|
||||||
|
if (stackResult.IsDirectory != file.IsDirectory
|
||||||
|
|| !string.Equals(partType, stackResult.PartType, StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| stackResult.ContainsPart(partNumber))
|
||||||
{
|
{
|
||||||
var file2 = list[j];
|
continue;
|
||||||
|
|
||||||
if (file1.IsDirectory != file2.IsDirectory)
|
|
||||||
{
|
|
||||||
j++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// (Title)(Volume)(Ignore)(Extension)
|
|
||||||
var match2 = FindMatch(file2, exp, offset);
|
|
||||||
|
|
||||||
if (match2.Success)
|
|
||||||
{
|
|
||||||
var title2 = match2.Groups[1].Value;
|
|
||||||
var volume2 = match2.Groups[2].Value;
|
|
||||||
var ignore2 = match2.Groups[3].Value;
|
|
||||||
var extension2 = match2.Groups[4].Value;
|
|
||||||
|
|
||||||
if (string.Equals(title1, title2, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
if (!string.Equals(volume1, volume2, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
if (string.Equals(ignore1, ignore2, StringComparison.OrdinalIgnoreCase)
|
|
||||||
&& string.Equals(extension1, extension2, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
if (stack.Files.Count == 0)
|
|
||||||
{
|
|
||||||
stack.Name = title1 + ignore1;
|
|
||||||
stack.IsDirectoryStack = file1.IsDirectory;
|
|
||||||
stack.Files.Add(file1.FullName);
|
|
||||||
}
|
|
||||||
|
|
||||||
stack.Files.Add(file2.FullName);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Sequel
|
|
||||||
offset = 0;
|
|
||||||
expressionIndex++;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (!string.Equals(ignore1, ignore2, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
// False positive, try again with offset
|
|
||||||
offset = match1.Groups[3].Index;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Extension mismatch
|
|
||||||
offset = 0;
|
|
||||||
expressionIndex++;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Title mismatch
|
|
||||||
offset = 0;
|
|
||||||
expressionIndex++;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// No match 2, next expression
|
|
||||||
offset = 0;
|
|
||||||
expressionIndex++;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
j++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (j == list.Count)
|
if (rule.IsNumerical != stackResult.IsNumerical)
|
||||||
{
|
{
|
||||||
expressionIndex = expressions.Length;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
// No match 1
|
|
||||||
offset = 0;
|
|
||||||
expressionIndex++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stack.Files.Count > 1)
|
stackResult.Parts.Add(partNumber, file);
|
||||||
{
|
break;
|
||||||
yield return stack;
|
|
||||||
i += stack.Files.Count - 1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private static string GetRegexInput(FileSystemMetadata file)
|
foreach (var (fileName, stack) in potentialStacks)
|
||||||
{
|
|
||||||
// For directories, dummy up an extension otherwise the expressions will fail
|
|
||||||
var input = !file.IsDirectory
|
|
||||||
? file.FullName
|
|
||||||
: file.FullName + ".mkv";
|
|
||||||
|
|
||||||
return Path.GetFileName(input);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Match FindMatch(FileSystemMetadata input, Regex regex, int offset)
|
|
||||||
{
|
|
||||||
var regexInput = GetRegexInput(input);
|
|
||||||
|
|
||||||
if (offset < 0 || offset >= regexInput.Length)
|
|
||||||
{
|
{
|
||||||
return Match.Empty;
|
if (stack.Parts.Count < 2)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
yield return new FileStack(fileName, stack.IsDirectory, stack.Parts.Select(kv => kv.Value.FullName).ToArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class StackMetadata
|
||||||
|
{
|
||||||
|
public StackMetadata(bool isDirectory, bool isNumerical, string partType)
|
||||||
|
{
|
||||||
|
Parts = new Dictionary<string, FileSystemMetadata>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
IsDirectory = isDirectory;
|
||||||
|
IsNumerical = isNumerical;
|
||||||
|
PartType = partType;
|
||||||
}
|
}
|
||||||
|
|
||||||
return regex.Match(regexInput, offset);
|
public Dictionary<string, FileSystemMetadata> Parts { get; }
|
||||||
|
|
||||||
|
public bool IsDirectory { get; }
|
||||||
|
|
||||||
|
public bool IsNumerical { get; }
|
||||||
|
|
||||||
|
public string PartType { get; }
|
||||||
|
|
||||||
|
public bool ContainsPart(string partNumber) => Parts.ContainsKey(partNumber);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
|
||||||
using Emby.Naming.Common;
|
using Emby.Naming.Common;
|
||||||
|
using Jellyfin.Extensions;
|
||||||
|
|
||||||
namespace Emby.Naming.Video
|
namespace Emby.Naming.Video
|
||||||
{
|
{
|
||||||
@@ -28,7 +28,7 @@ namespace Emby.Naming.Video
|
|||||||
|
|
||||||
var extension = Path.GetExtension(path);
|
var extension = Path.GetExtension(path);
|
||||||
|
|
||||||
if (!options.StubFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
|
if (!options.StubFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using MediaBrowser.Model.Entities;
|
||||||
|
|
||||||
namespace Emby.Naming.Video
|
namespace Emby.Naming.Video
|
||||||
{
|
{
|
||||||
@@ -17,7 +18,6 @@ namespace Emby.Naming.Video
|
|||||||
Name = name;
|
Name = name;
|
||||||
|
|
||||||
Files = Array.Empty<VideoFileInfo>();
|
Files = Array.Empty<VideoFileInfo>();
|
||||||
Extras = Array.Empty<VideoFileInfo>();
|
|
||||||
AlternateVersions = Array.Empty<VideoFileInfo>();
|
AlternateVersions = Array.Empty<VideoFileInfo>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,16 +39,15 @@ namespace Emby.Naming.Video
|
|||||||
/// <value>The files.</value>
|
/// <value>The files.</value>
|
||||||
public IReadOnlyList<VideoFileInfo> Files { get; set; }
|
public IReadOnlyList<VideoFileInfo> Files { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the extras.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The extras.</value>
|
|
||||||
public IReadOnlyList<VideoFileInfo> Extras { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the alternate versions.
|
/// Gets or sets the alternate versions.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The alternate versions.</value>
|
/// <value>The alternate versions.</value>
|
||||||
public IReadOnlyList<VideoFileInfo> AlternateVersions { get; set; }
|
public IReadOnlyList<VideoFileInfo> AlternateVersions { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the extra type.
|
||||||
|
/// </summary>
|
||||||
|
public ExtraType? ExtraType { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ using System.IO;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using Emby.Naming.Common;
|
using Emby.Naming.Common;
|
||||||
using MediaBrowser.Model.Entities;
|
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
|
|
||||||
namespace Emby.Naming.Video
|
namespace Emby.Naming.Video
|
||||||
@@ -17,29 +16,41 @@ namespace Emby.Naming.Video
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Resolves alternative versions and extras from list of video files.
|
/// Resolves alternative versions and extras from list of video files.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="files">List of related video files.</param>
|
/// <param name="videoInfos">List of related video files.</param>
|
||||||
/// <param name="namingOptions">The naming options.</param>
|
/// <param name="namingOptions">The naming options.</param>
|
||||||
/// <param name="supportMultiVersion">Indication we should consider multi-versions of content.</param>
|
/// <param name="supportMultiVersion">Indication we should consider multi-versions of content.</param>
|
||||||
|
/// <param name="parseName">Whether to parse the name or use the filename.</param>
|
||||||
/// <returns>Returns enumerable of <see cref="VideoInfo"/> which groups files together when related.</returns>
|
/// <returns>Returns enumerable of <see cref="VideoInfo"/> which groups files together when related.</returns>
|
||||||
public static IEnumerable<VideoInfo> Resolve(IEnumerable<FileSystemMetadata> files, NamingOptions namingOptions, bool supportMultiVersion = true)
|
public static IReadOnlyList<VideoInfo> Resolve(IReadOnlyList<VideoFileInfo> videoInfos, NamingOptions namingOptions, bool supportMultiVersion = true, bool parseName = true)
|
||||||
{
|
{
|
||||||
var videoInfos = files
|
|
||||||
.Select(i => VideoResolver.Resolve(i.FullName, i.IsDirectory, namingOptions))
|
|
||||||
.OfType<VideoFileInfo>()
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
// Filter out all extras, otherwise they could cause stacks to not be resolved
|
// Filter out all extras, otherwise they could cause stacks to not be resolved
|
||||||
// See the unit test TestStackedWithTrailer
|
// See the unit test TestStackedWithTrailer
|
||||||
var nonExtras = videoInfos
|
var nonExtras = videoInfos
|
||||||
.Where(i => i.ExtraType == null)
|
.Where(i => i.ExtraType == null)
|
||||||
.Select(i => new FileSystemMetadata { FullName = i.Path, IsDirectory = i.IsDirectory });
|
.Select(i => new FileSystemMetadata { FullName = i.Path, IsDirectory = i.IsDirectory });
|
||||||
|
|
||||||
var stackResult = new StackResolver(namingOptions)
|
var stackResult = StackResolver.Resolve(nonExtras, namingOptions).ToList();
|
||||||
.Resolve(nonExtras).ToList();
|
|
||||||
|
|
||||||
var remainingFiles = videoInfos
|
var remainingFiles = new List<VideoFileInfo>();
|
||||||
.Where(i => !stackResult.Any(s => i.Path != null && s.ContainsFile(i.Path, i.IsDirectory)))
|
var standaloneMedia = new List<VideoFileInfo>();
|
||||||
.ToList();
|
|
||||||
|
for (var i = 0; i < videoInfos.Count; i++)
|
||||||
|
{
|
||||||
|
var current = videoInfos[i];
|
||||||
|
if (stackResult.Any(s => s.ContainsFile(current.Path, current.IsDirectory)))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current.ExtraType == null)
|
||||||
|
{
|
||||||
|
standaloneMedia.Add(current);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
remainingFiles.Add(current);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var list = new List<VideoInfo>();
|
var list = new List<VideoInfo>();
|
||||||
|
|
||||||
@@ -47,38 +58,20 @@ namespace Emby.Naming.Video
|
|||||||
{
|
{
|
||||||
var info = new VideoInfo(stack.Name)
|
var info = new VideoInfo(stack.Name)
|
||||||
{
|
{
|
||||||
Files = stack.Files.Select(i => VideoResolver.Resolve(i, stack.IsDirectoryStack, namingOptions))
|
Files = stack.Files.Select(i => VideoResolver.Resolve(i, stack.IsDirectoryStack, namingOptions, parseName))
|
||||||
.OfType<VideoFileInfo>()
|
.OfType<VideoFileInfo>()
|
||||||
.ToList()
|
.ToList()
|
||||||
};
|
};
|
||||||
|
|
||||||
info.Year = info.Files[0].Year;
|
info.Year = info.Files[0].Year;
|
||||||
|
|
||||||
var extras = ExtractExtras(remainingFiles, stack.Name, Path.GetFileNameWithoutExtension(stack.Files[0].AsSpan()), namingOptions.VideoFlagDelimiters);
|
|
||||||
|
|
||||||
if (extras.Count > 0)
|
|
||||||
{
|
|
||||||
info.Extras = extras;
|
|
||||||
}
|
|
||||||
|
|
||||||
list.Add(info);
|
list.Add(info);
|
||||||
}
|
}
|
||||||
|
|
||||||
var standaloneMedia = remainingFiles
|
|
||||||
.Where(i => i.ExtraType == null)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
foreach (var media in standaloneMedia)
|
foreach (var media in standaloneMedia)
|
||||||
{
|
{
|
||||||
var info = new VideoInfo(media.Name) { Files = new[] { media } };
|
var info = new VideoInfo(media.Name) { Files = new[] { media } };
|
||||||
|
|
||||||
info.Year = info.Files[0].Year;
|
info.Year = info.Files[0].Year;
|
||||||
|
|
||||||
remainingFiles.Remove(media);
|
|
||||||
var extras = ExtractExtras(remainingFiles, media.FileNameWithoutExtension, namingOptions.VideoFlagDelimiters);
|
|
||||||
|
|
||||||
info.Extras = extras;
|
|
||||||
|
|
||||||
list.Add(info);
|
list.Add(info);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,58 +80,12 @@ namespace Emby.Naming.Video
|
|||||||
list = GetVideosGroupedByVersion(list, namingOptions);
|
list = GetVideosGroupedByVersion(list, namingOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there's only one resolved video, use the folder name as well to find extras
|
|
||||||
if (list.Count == 1)
|
|
||||||
{
|
|
||||||
var info = list[0];
|
|
||||||
var videoPath = list[0].Files[0].Path;
|
|
||||||
var parentPath = Path.GetDirectoryName(videoPath.AsSpan());
|
|
||||||
|
|
||||||
if (!parentPath.IsEmpty)
|
|
||||||
{
|
|
||||||
var folderName = Path.GetFileName(parentPath);
|
|
||||||
if (!folderName.IsEmpty)
|
|
||||||
{
|
|
||||||
var extras = ExtractExtras(remainingFiles, folderName, namingOptions.VideoFlagDelimiters);
|
|
||||||
extras.AddRange(info.Extras);
|
|
||||||
info.Extras = extras;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the extras that are just based on file name as well
|
|
||||||
var extrasByFileName = remainingFiles
|
|
||||||
.Where(i => i.ExtraRule != null && i.ExtraRule.RuleType == ExtraRuleType.Filename)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
remainingFiles = remainingFiles
|
|
||||||
.Except(extrasByFileName)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
extrasByFileName.AddRange(info.Extras);
|
|
||||||
info.Extras = extrasByFileName;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there's only one video, accept all trailers
|
|
||||||
// Be lenient because people use all kinds of mishmash conventions with trailers.
|
|
||||||
if (list.Count == 1)
|
|
||||||
{
|
|
||||||
var trailers = remainingFiles
|
|
||||||
.Where(i => i.ExtraType == ExtraType.Trailer)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
trailers.AddRange(list[0].Extras);
|
|
||||||
list[0].Extras = trailers;
|
|
||||||
|
|
||||||
remainingFiles = remainingFiles
|
|
||||||
.Except(trailers)
|
|
||||||
.ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Whatever files are left, just add them
|
// Whatever files are left, just add them
|
||||||
list.AddRange(remainingFiles.Select(i => new VideoInfo(i.Name)
|
list.AddRange(remainingFiles.Select(i => new VideoInfo(i.Name)
|
||||||
{
|
{
|
||||||
Files = new[] { i },
|
Files = new[] { i },
|
||||||
Year = i.Year
|
Year = i.Year,
|
||||||
|
ExtraType = i.ExtraType
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return list;
|
return list;
|
||||||
@@ -162,6 +109,11 @@ namespace Emby.Naming.Video
|
|||||||
for (var i = 0; i < videos.Count; i++)
|
for (var i = 0; i < videos.Count; i++)
|
||||||
{
|
{
|
||||||
var video = videos[i];
|
var video = videos[i];
|
||||||
|
if (video.ExtraType != null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (!IsEligibleForMultiVersion(folderName, video.Files[0].Path, namingOptions))
|
if (!IsEligibleForMultiVersion(folderName, video.Files[0].Path, namingOptions))
|
||||||
{
|
{
|
||||||
return videos;
|
return videos;
|
||||||
@@ -178,17 +130,14 @@ namespace Emby.Naming.Video
|
|||||||
|
|
||||||
var alternateVersionsLen = videos.Count - 1;
|
var alternateVersionsLen = videos.Count - 1;
|
||||||
var alternateVersions = new VideoFileInfo[alternateVersionsLen];
|
var alternateVersions = new VideoFileInfo[alternateVersionsLen];
|
||||||
var extras = new List<VideoFileInfo>(list[0].Extras);
|
|
||||||
for (int i = 0; i < alternateVersionsLen; i++)
|
for (int i = 0; i < alternateVersionsLen; i++)
|
||||||
{
|
{
|
||||||
var video = videos[i + 1];
|
var video = videos[i + 1];
|
||||||
alternateVersions[i] = video.Files[0];
|
alternateVersions[i] = video.Files[0];
|
||||||
extras.AddRange(video.Extras);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
list[0].AlternateVersions = alternateVersions;
|
list[0].AlternateVersions = alternateVersions;
|
||||||
list[0].Name = folderName.ToString();
|
list[0].Name = folderName.ToString();
|
||||||
list[0].Extras = extras;
|
|
||||||
|
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
@@ -230,7 +179,7 @@ namespace Emby.Naming.Video
|
|||||||
var tmpTestFilename = testFilename.ToString();
|
var tmpTestFilename = testFilename.ToString();
|
||||||
if (CleanStringParser.TryClean(tmpTestFilename, namingOptions.CleanStringRegexes, out var cleanName))
|
if (CleanStringParser.TryClean(tmpTestFilename, namingOptions.CleanStringRegexes, out var cleanName))
|
||||||
{
|
{
|
||||||
tmpTestFilename = cleanName.Trim().ToString();
|
tmpTestFilename = cleanName.Trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
// The CleanStringParser should have removed common keywords etc.
|
// The CleanStringParser should have removed common keywords etc.
|
||||||
@@ -238,67 +187,5 @@ namespace Emby.Naming.Video
|
|||||||
|| testFilename[0] == '-'
|
|| testFilename[0] == '-'
|
||||||
|| Regex.IsMatch(tmpTestFilename, @"^\[([^]]*)\]", RegexOptions.Compiled);
|
|| Regex.IsMatch(tmpTestFilename, @"^\[([^]]*)\]", RegexOptions.Compiled);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ReadOnlySpan<char> TrimFilenameDelimiters(ReadOnlySpan<char> name, ReadOnlySpan<char> videoFlagDelimiters)
|
|
||||||
{
|
|
||||||
return name.IsEmpty ? name : name.TrimEnd().TrimEnd(videoFlagDelimiters).TrimEnd();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool StartsWith(ReadOnlySpan<char> fileName, ReadOnlySpan<char> baseName, ReadOnlySpan<char> trimmedBaseName)
|
|
||||||
{
|
|
||||||
if (baseName.IsEmpty)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return fileName.StartsWith(baseName, StringComparison.OrdinalIgnoreCase)
|
|
||||||
|| (!trimmedBaseName.IsEmpty && fileName.StartsWith(trimmedBaseName, StringComparison.OrdinalIgnoreCase));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Finds similar filenames to that of [baseName] and removes any matches from [remainingFiles].
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="remainingFiles">The list of remaining filenames.</param>
|
|
||||||
/// <param name="baseName">The base name to use for the comparison.</param>
|
|
||||||
/// <param name="videoFlagDelimiters">The video flag delimiters.</param>
|
|
||||||
/// <returns>A list of video extras for [baseName].</returns>
|
|
||||||
private static List<VideoFileInfo> ExtractExtras(IList<VideoFileInfo> remainingFiles, ReadOnlySpan<char> baseName, ReadOnlySpan<char> videoFlagDelimiters)
|
|
||||||
{
|
|
||||||
return ExtractExtras(remainingFiles, baseName, ReadOnlySpan<char>.Empty, videoFlagDelimiters);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Finds similar filenames to that of [firstBaseName] and [secondBaseName] and removes any matches from [remainingFiles].
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="remainingFiles">The list of remaining filenames.</param>
|
|
||||||
/// <param name="firstBaseName">The first base name to use for the comparison.</param>
|
|
||||||
/// <param name="secondBaseName">The second base name to use for the comparison.</param>
|
|
||||||
/// <param name="videoFlagDelimiters">The video flag delimiters.</param>
|
|
||||||
/// <returns>A list of video extras for [firstBaseName] and [secondBaseName].</returns>
|
|
||||||
private static List<VideoFileInfo> ExtractExtras(IList<VideoFileInfo> remainingFiles, ReadOnlySpan<char> firstBaseName, ReadOnlySpan<char> secondBaseName, ReadOnlySpan<char> videoFlagDelimiters)
|
|
||||||
{
|
|
||||||
var trimmedFirstBaseName = TrimFilenameDelimiters(firstBaseName, videoFlagDelimiters);
|
|
||||||
var trimmedSecondBaseName = TrimFilenameDelimiters(secondBaseName, videoFlagDelimiters);
|
|
||||||
|
|
||||||
var result = new List<VideoFileInfo>();
|
|
||||||
for (var pos = remainingFiles.Count - 1; pos >= 0; pos--)
|
|
||||||
{
|
|
||||||
var file = remainingFiles[pos];
|
|
||||||
if (file.ExtraType == null)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var filename = file.FileNameWithoutExtension;
|
|
||||||
if (StartsWith(filename, firstBaseName, trimmedFirstBaseName)
|
|
||||||
|| StartsWith(filename, secondBaseName, trimmedSecondBaseName))
|
|
||||||
{
|
|
||||||
result.Add(file);
|
|
||||||
remainingFiles.RemoveAt(pos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,10 +16,11 @@ namespace Emby.Naming.Video
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="path">The path.</param>
|
/// <param name="path">The path.</param>
|
||||||
/// <param name="namingOptions">The naming options.</param>
|
/// <param name="namingOptions">The naming options.</param>
|
||||||
|
/// <param name="parseName">Whether to parse the name or use the filename.</param>
|
||||||
/// <returns>VideoFileInfo.</returns>
|
/// <returns>VideoFileInfo.</returns>
|
||||||
public static VideoFileInfo? ResolveDirectory(string? path, NamingOptions namingOptions)
|
public static VideoFileInfo? ResolveDirectory(string? path, NamingOptions namingOptions, bool parseName = true)
|
||||||
{
|
{
|
||||||
return Resolve(path, true, namingOptions);
|
return Resolve(path, true, namingOptions, parseName);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -74,7 +75,7 @@ namespace Emby.Naming.Video
|
|||||||
|
|
||||||
var format3DResult = Format3DParser.Parse(path, namingOptions);
|
var format3DResult = Format3DParser.Parse(path, namingOptions);
|
||||||
|
|
||||||
var extraResult = new ExtraResolver(namingOptions).GetExtraInfo(path);
|
var extraResult = ExtraRuleResolver.GetExtraInfo(path, namingOptions);
|
||||||
|
|
||||||
var name = Path.GetFileNameWithoutExtension(path);
|
var name = Path.GetFileNameWithoutExtension(path);
|
||||||
|
|
||||||
|
|||||||
@@ -24,63 +24,63 @@ namespace Emby.Notifications
|
|||||||
{
|
{
|
||||||
new NotificationTypeInfo
|
new NotificationTypeInfo
|
||||||
{
|
{
|
||||||
Type = NotificationType.ApplicationUpdateInstalled.ToString()
|
Type = nameof(NotificationType.ApplicationUpdateInstalled)
|
||||||
},
|
},
|
||||||
new NotificationTypeInfo
|
new NotificationTypeInfo
|
||||||
{
|
{
|
||||||
Type = NotificationType.InstallationFailed.ToString()
|
Type = nameof(NotificationType.InstallationFailed)
|
||||||
},
|
},
|
||||||
new NotificationTypeInfo
|
new NotificationTypeInfo
|
||||||
{
|
{
|
||||||
Type = NotificationType.PluginInstalled.ToString()
|
Type = nameof(NotificationType.PluginInstalled)
|
||||||
},
|
},
|
||||||
new NotificationTypeInfo
|
new NotificationTypeInfo
|
||||||
{
|
{
|
||||||
Type = NotificationType.PluginError.ToString()
|
Type = nameof(NotificationType.PluginError)
|
||||||
},
|
},
|
||||||
new NotificationTypeInfo
|
new NotificationTypeInfo
|
||||||
{
|
{
|
||||||
Type = NotificationType.PluginUninstalled.ToString()
|
Type = nameof(NotificationType.PluginUninstalled)
|
||||||
},
|
},
|
||||||
new NotificationTypeInfo
|
new NotificationTypeInfo
|
||||||
{
|
{
|
||||||
Type = NotificationType.PluginUpdateInstalled.ToString()
|
Type = nameof(NotificationType.PluginUpdateInstalled)
|
||||||
},
|
},
|
||||||
new NotificationTypeInfo
|
new NotificationTypeInfo
|
||||||
{
|
{
|
||||||
Type = NotificationType.ServerRestartRequired.ToString()
|
Type = nameof(NotificationType.ServerRestartRequired)
|
||||||
},
|
},
|
||||||
new NotificationTypeInfo
|
new NotificationTypeInfo
|
||||||
{
|
{
|
||||||
Type = NotificationType.TaskFailed.ToString()
|
Type = nameof(NotificationType.TaskFailed)
|
||||||
},
|
},
|
||||||
new NotificationTypeInfo
|
new NotificationTypeInfo
|
||||||
{
|
{
|
||||||
Type = NotificationType.NewLibraryContent.ToString()
|
Type = nameof(NotificationType.NewLibraryContent)
|
||||||
},
|
},
|
||||||
new NotificationTypeInfo
|
new NotificationTypeInfo
|
||||||
{
|
{
|
||||||
Type = NotificationType.AudioPlayback.ToString()
|
Type = nameof(NotificationType.AudioPlayback)
|
||||||
},
|
},
|
||||||
new NotificationTypeInfo
|
new NotificationTypeInfo
|
||||||
{
|
{
|
||||||
Type = NotificationType.VideoPlayback.ToString()
|
Type = nameof(NotificationType.VideoPlayback)
|
||||||
},
|
},
|
||||||
new NotificationTypeInfo
|
new NotificationTypeInfo
|
||||||
{
|
{
|
||||||
Type = NotificationType.AudioPlaybackStopped.ToString()
|
Type = nameof(NotificationType.AudioPlaybackStopped)
|
||||||
},
|
},
|
||||||
new NotificationTypeInfo
|
new NotificationTypeInfo
|
||||||
{
|
{
|
||||||
Type = NotificationType.VideoPlaybackStopped.ToString()
|
Type = nameof(NotificationType.VideoPlaybackStopped)
|
||||||
},
|
},
|
||||||
new NotificationTypeInfo
|
new NotificationTypeInfo
|
||||||
{
|
{
|
||||||
Type = NotificationType.UserLockedOut.ToString()
|
Type = nameof(NotificationType.UserLockedOut)
|
||||||
},
|
},
|
||||||
new NotificationTypeInfo
|
new NotificationTypeInfo
|
||||||
{
|
{
|
||||||
Type = NotificationType.ApplicationUpdateAvailable.ToString()
|
Type = nameof(NotificationType.ApplicationUpdateAvailable)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -98,7 +98,7 @@ namespace Emby.Notifications
|
|||||||
|
|
||||||
private void Update(NotificationTypeInfo note)
|
private void Update(NotificationTypeInfo note)
|
||||||
{
|
{
|
||||||
note.Name = _localization.GetLocalizedString("NotificationOption" + note.Type) ?? note.Type;
|
note.Name = _localization.GetLocalizedString("NotificationOption" + note.Type);
|
||||||
|
|
||||||
note.IsBasedOnUserEvent = note.Type.IndexOf("Playback", StringComparison.OrdinalIgnoreCase) != -1;
|
note.IsBasedOnUserEvent = note.Type.IndexOf("Playback", StringComparison.OrdinalIgnoreCase) != -1;
|
||||||
|
|
||||||
|
|||||||
@@ -23,8 +23,12 @@
|
|||||||
|
|
||||||
<!-- Code analyzers-->
|
<!-- Code analyzers-->
|
||||||
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||||
|
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.3">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
|
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
|
||||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
|
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.406" PrivateAssets="All" />
|
||||||
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
|
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using System.Linq;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Jellyfin.Data.Events;
|
using Jellyfin.Data.Events;
|
||||||
|
using Jellyfin.Extensions;
|
||||||
using MediaBrowser.Common.Configuration;
|
using MediaBrowser.Common.Configuration;
|
||||||
using MediaBrowser.Controller;
|
using MediaBrowser.Controller;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
@@ -104,14 +105,14 @@ namespace Emby.Notifications
|
|||||||
|
|
||||||
var type = entry.Type;
|
var type = entry.Type;
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(type) || !_coreNotificationTypes.Contains(type, StringComparer.OrdinalIgnoreCase))
|
if (string.IsNullOrEmpty(type) || !_coreNotificationTypes.Contains(type, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var userId = e.Argument.UserId;
|
var userId = e.Argument.UserId;
|
||||||
|
|
||||||
if (!userId.Equals(Guid.Empty) && !GetOptions().IsEnabledToMonitorUser(type, userId))
|
if (!userId.Equals(default) && !GetOptions().IsEnabledToMonitorUser(type, userId))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,11 @@
|
|||||||
|
|
||||||
<!-- Code Analyzers-->
|
<!-- Code Analyzers-->
|
||||||
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
|
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.3">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.406" PrivateAssets="All" />
|
||||||
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
|
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
|
||||||
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
|
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using System.IO;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Jellyfin.Extensions;
|
||||||
using MediaBrowser.Controller.Drawing;
|
using MediaBrowser.Controller.Drawing;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
@@ -60,7 +61,7 @@ namespace Emby.Photos
|
|||||||
item.SetImagePath(ImageType.Primary, item.Path);
|
item.SetImagePath(ImageType.Primary, item.Path);
|
||||||
|
|
||||||
// Examples: https://github.com/mono/taglib-sharp/blob/a5f6949a53d09ce63ee7495580d6802921a21f14/tests/fixtures/TagLib.Tests.Images/NullOrientationTest.cs
|
// Examples: https://github.com/mono/taglib-sharp/blob/a5f6949a53d09ce63ee7495580d6802921a21f14/tests/fixtures/TagLib.Tests.Images/NullOrientationTest.cs
|
||||||
if (_includeExtensions.Contains(Path.GetExtension(item.Path), StringComparer.OrdinalIgnoreCase))
|
if (_includeExtensions.Contains(Path.GetExtension(item.Path), StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -301,7 +301,7 @@ namespace Emby.Server.Implementations.AppBase
|
|||||||
{
|
{
|
||||||
return _configurations.GetOrAdd(
|
return _configurations.GetOrAdd(
|
||||||
key,
|
key,
|
||||||
(k, configurationManager) =>
|
static (k, configurationManager) =>
|
||||||
{
|
{
|
||||||
var file = configurationManager.GetConfigurationFile(k);
|
var file = configurationManager.GetConfigurationFile(k);
|
||||||
|
|
||||||
@@ -371,7 +371,7 @@ namespace Emby.Server.Implementations.AppBase
|
|||||||
NewConfiguration = configuration
|
NewConfiguration = configuration
|
||||||
});
|
});
|
||||||
|
|
||||||
_configurations.AddOrUpdate(key, configuration, (k, v) => configuration);
|
_configurations.AddOrUpdate(key, configuration, (_, _) => configuration);
|
||||||
|
|
||||||
var path = GetConfigurationFile(key);
|
var path = GetConfigurationFile(key);
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
||||||
@@ -398,6 +398,12 @@ namespace Emby.Server.Implementations.AppBase
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ConfigurationStore[] GetConfigurationStores()
|
||||||
|
{
|
||||||
|
return _configurationStores;
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Type GetConfigurationType(string key)
|
public Type GetConfigurationType(string key)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
|
||||||
using MediaBrowser.Model.Serialization;
|
using MediaBrowser.Model.Serialization;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.AppBase
|
namespace Emby.Server.Implementations.AppBase
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ using Emby.Dlna;
|
|||||||
using Emby.Dlna.Main;
|
using Emby.Dlna.Main;
|
||||||
using Emby.Dlna.Ssdp;
|
using Emby.Dlna.Ssdp;
|
||||||
using Emby.Drawing;
|
using Emby.Drawing;
|
||||||
|
using Emby.Naming.Common;
|
||||||
using Emby.Notifications;
|
using Emby.Notifications;
|
||||||
using Emby.Photos;
|
using Emby.Photos;
|
||||||
using Emby.Server.Implementations.Archiving;
|
using Emby.Server.Implementations.Archiving;
|
||||||
@@ -43,9 +44,9 @@ using Emby.Server.Implementations.Serialization;
|
|||||||
using Emby.Server.Implementations.Session;
|
using Emby.Server.Implementations.Session;
|
||||||
using Emby.Server.Implementations.SyncPlay;
|
using Emby.Server.Implementations.SyncPlay;
|
||||||
using Emby.Server.Implementations.TV;
|
using Emby.Server.Implementations.TV;
|
||||||
using Emby.Server.Implementations.Udp;
|
|
||||||
using Emby.Server.Implementations.Updates;
|
using Emby.Server.Implementations.Updates;
|
||||||
using Jellyfin.Api.Helpers;
|
using Jellyfin.Api.Helpers;
|
||||||
|
using Jellyfin.MediaEncoding.Hls.Playlist;
|
||||||
using Jellyfin.Networking.Configuration;
|
using Jellyfin.Networking.Configuration;
|
||||||
using Jellyfin.Networking.Manager;
|
using Jellyfin.Networking.Manager;
|
||||||
using MediaBrowser.Common;
|
using MediaBrowser.Common;
|
||||||
@@ -102,6 +103,7 @@ using Microsoft.Extensions.Configuration;
|
|||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Prometheus.DotNetRuntime;
|
using Prometheus.DotNetRuntime;
|
||||||
|
using static MediaBrowser.Controller.Extensions.ConfigurationExtensions;
|
||||||
using WebSocketManager = Emby.Server.Implementations.HttpServer.WebSocketManager;
|
using WebSocketManager = Emby.Server.Implementations.HttpServer.WebSocketManager;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations
|
namespace Emby.Server.Implementations
|
||||||
@@ -119,7 +121,7 @@ namespace Emby.Server.Implementations
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The disposable parts.
|
/// The disposable parts.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly ConcurrentDictionary<IDisposable, byte> _disposableParts = new ();
|
private readonly ConcurrentDictionary<IDisposable, byte> _disposableParts = new();
|
||||||
|
|
||||||
private readonly IFileSystem _fileSystemManager;
|
private readonly IFileSystem _fileSystemManager;
|
||||||
private readonly IConfiguration _startupConfig;
|
private readonly IConfiguration _startupConfig;
|
||||||
@@ -148,7 +150,7 @@ namespace Emby.Server.Implementations
|
|||||||
/// <param name="loggerFactory">Instance of the <see cref="ILoggerFactory"/> interface.</param>
|
/// <param name="loggerFactory">Instance of the <see cref="ILoggerFactory"/> interface.</param>
|
||||||
/// <param name="options">Instance of the <see cref="IStartupOptions"/> interface.</param>
|
/// <param name="options">Instance of the <see cref="IStartupOptions"/> interface.</param>
|
||||||
/// <param name="startupConfig">The <see cref="IConfiguration" /> interface.</param>
|
/// <param name="startupConfig">The <see cref="IConfiguration" /> interface.</param>
|
||||||
public ApplicationHost(
|
protected ApplicationHost(
|
||||||
IServerApplicationPaths applicationPaths,
|
IServerApplicationPaths applicationPaths,
|
||||||
ILoggerFactory loggerFactory,
|
ILoggerFactory loggerFactory,
|
||||||
IStartupOptions options,
|
IStartupOptions options,
|
||||||
@@ -182,6 +184,11 @@ namespace Emby.Server.Implementations
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public event EventHandler HasPendingRestartChanged;
|
public event EventHandler HasPendingRestartChanged;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the value of the PublishedServerUrl setting.
|
||||||
|
/// </summary>
|
||||||
|
private string PublishedServerUrl => _startupConfig[AddressOverrideKey];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether this instance can self restart.
|
/// Gets a value indicating whether this instance can self restart.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -258,11 +265,6 @@ namespace Emby.Server.Implementations
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public int HttpsPort { get; private set; }
|
public int HttpsPort { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the value of the PublishedServerUrl setting.
|
|
||||||
/// </summary>
|
|
||||||
public string PublishedServerUrl => _startupOptions.PublishedServerUrl ?? _startupConfig[UdpServer.AddressOverrideConfigKey];
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Version ApplicationVersion { get; }
|
public Version ApplicationVersion { get; }
|
||||||
|
|
||||||
@@ -312,22 +314,6 @@ namespace Emby.Server.Implementations
|
|||||||
? Environment.MachineName
|
? Environment.MachineName
|
||||||
: ConfigurationManager.Configuration.ServerName;
|
: ConfigurationManager.Configuration.ServerName;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Temporary function to migration network settings out of system.xml and into network.xml.
|
|
||||||
/// TODO: remove at the point when a fixed migration path has been decided upon.
|
|
||||||
/// </summary>
|
|
||||||
private void MigrateNetworkConfiguration()
|
|
||||||
{
|
|
||||||
string path = Path.Combine(ConfigurationManager.CommonApplicationPaths.ConfigurationDirectoryPath, "network.xml");
|
|
||||||
if (!File.Exists(path))
|
|
||||||
{
|
|
||||||
var networkSettings = new NetworkConfiguration();
|
|
||||||
ClassMigrationHelper.CopyProperties(ConfigurationManager.Configuration, networkSettings);
|
|
||||||
_xmlSerializer.SerializeToFile(networkSettings, path);
|
|
||||||
Logger.LogDebug("Successfully migrated network settings.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public string ExpandVirtualPath(string path)
|
public string ExpandVirtualPath(string path)
|
||||||
{
|
{
|
||||||
var appPaths = ApplicationPaths;
|
var appPaths = ApplicationPaths;
|
||||||
@@ -512,8 +498,6 @@ namespace Emby.Server.Implementations
|
|||||||
|
|
||||||
ConfigurationManager.AddParts(GetExports<IConfigurationFactory>());
|
ConfigurationManager.AddParts(GetExports<IConfigurationFactory>());
|
||||||
|
|
||||||
// Have to migrate settings here as migration subsystem not yet initialised.
|
|
||||||
MigrateNetworkConfiguration();
|
|
||||||
NetManager = new NetworkManager(ConfigurationManager, LoggerFactory.CreateLogger<NetworkManager>());
|
NetManager = new NetworkManager(ConfigurationManager, LoggerFactory.CreateLogger<NetworkManager>());
|
||||||
|
|
||||||
// Initialize runtime stat collection
|
// Initialize runtime stat collection
|
||||||
@@ -596,6 +580,7 @@ namespace Emby.Server.Implementations
|
|||||||
serviceCollection.AddTransient(provider => new Lazy<IProviderManager>(provider.GetRequiredService<IProviderManager>));
|
serviceCollection.AddTransient(provider => new Lazy<IProviderManager>(provider.GetRequiredService<IProviderManager>));
|
||||||
serviceCollection.AddTransient(provider => new Lazy<IUserViewManager>(provider.GetRequiredService<IUserViewManager>));
|
serviceCollection.AddTransient(provider => new Lazy<IUserViewManager>(provider.GetRequiredService<IUserViewManager>));
|
||||||
serviceCollection.AddSingleton<ILibraryManager, LibraryManager>();
|
serviceCollection.AddSingleton<ILibraryManager, LibraryManager>();
|
||||||
|
serviceCollection.AddSingleton<NamingOptions>();
|
||||||
|
|
||||||
serviceCollection.AddSingleton<IMusicManager, MusicManager>();
|
serviceCollection.AddSingleton<IMusicManager, MusicManager>();
|
||||||
|
|
||||||
@@ -989,7 +974,7 @@ namespace Emby.Server.Implementations
|
|||||||
yield return typeof(IServerApplicationHost).Assembly;
|
yield return typeof(IServerApplicationHost).Assembly;
|
||||||
|
|
||||||
// Include composable parts in the Providers assembly
|
// Include composable parts in the Providers assembly
|
||||||
yield return typeof(ProviderUtils).Assembly;
|
yield return typeof(ProviderManager).Assembly;
|
||||||
|
|
||||||
// Include composable parts in the Photos assembly
|
// Include composable parts in the Photos assembly
|
||||||
yield return typeof(PhotoProvider).Assembly;
|
yield return typeof(PhotoProvider).Assembly;
|
||||||
@@ -1015,6 +1000,9 @@ namespace Emby.Server.Implementations
|
|||||||
// Network
|
// Network
|
||||||
yield return typeof(NetworkManager).Assembly;
|
yield return typeof(NetworkManager).Assembly;
|
||||||
|
|
||||||
|
// Hls
|
||||||
|
yield return typeof(DynamicHlsPlaylistGenerator).Assembly;
|
||||||
|
|
||||||
foreach (var i in GetAssembliesWithPartsInternal())
|
foreach (var i in GetAssembliesWithPartsInternal())
|
||||||
{
|
{
|
||||||
yield return i;
|
yield return i;
|
||||||
@@ -1126,12 +1114,12 @@ namespace Emby.Server.Implementations
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public string GetApiUrlForLocalAccess(bool allowHttps)
|
public string GetApiUrlForLocalAccess(bool allowHttps = true)
|
||||||
{
|
{
|
||||||
// With an empty source, the port will be null
|
// With an empty source, the port will be null
|
||||||
string smart = NetManager.GetBindInterface(string.Empty, out _);
|
string smart = NetManager.GetBindInterface(string.Empty, out _);
|
||||||
var scheme = allowHttps ? Uri.UriSchemeHttps : Uri.UriSchemeHttp;
|
var scheme = !allowHttps ? Uri.UriSchemeHttp : null;
|
||||||
var port = allowHttps ? HttpsPort : HttpPort;
|
int? port = !allowHttps ? HttpPort : null;
|
||||||
return GetLocalApiUrl(smart.Trim('/'), scheme, port);
|
return GetLocalApiUrl(smart.Trim('/'), scheme, port);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
using SharpCompress.Archives.SevenZip;
|
|
||||||
using SharpCompress.Archives.Tar;
|
|
||||||
using SharpCompress.Common;
|
using SharpCompress.Common;
|
||||||
using SharpCompress.Readers;
|
using SharpCompress.Readers;
|
||||||
using SharpCompress.Readers.GZip;
|
using SharpCompress.Readers.GZip;
|
||||||
using SharpCompress.Readers.Zip;
|
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Archiving
|
namespace Emby.Server.Implementations.Archiving
|
||||||
{
|
{
|
||||||
@@ -14,55 +11,6 @@ namespace Emby.Server.Implementations.Archiving
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class ZipClient : IZipClient
|
public class ZipClient : IZipClient
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Extracts all.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="sourceFile">The source file.</param>
|
|
||||||
/// <param name="targetPath">The target path.</param>
|
|
||||||
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
|
|
||||||
public void ExtractAll(string sourceFile, string targetPath, bool overwriteExistingFiles)
|
|
||||||
{
|
|
||||||
using var fileStream = File.OpenRead(sourceFile);
|
|
||||||
ExtractAll(fileStream, targetPath, overwriteExistingFiles);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Extracts all.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="source">The source.</param>
|
|
||||||
/// <param name="targetPath">The target path.</param>
|
|
||||||
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
|
|
||||||
public void ExtractAll(Stream source, string targetPath, bool overwriteExistingFiles)
|
|
||||||
{
|
|
||||||
using var reader = ReaderFactory.Open(source);
|
|
||||||
var options = new ExtractionOptions
|
|
||||||
{
|
|
||||||
ExtractFullPath = true
|
|
||||||
};
|
|
||||||
|
|
||||||
if (overwriteExistingFiles)
|
|
||||||
{
|
|
||||||
options.Overwrite = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Directory.CreateDirectory(targetPath);
|
|
||||||
reader.WriteAllToDirectory(targetPath, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public void ExtractAllFromZip(Stream source, string targetPath, bool overwriteExistingFiles)
|
|
||||||
{
|
|
||||||
using var reader = ZipReader.Open(source);
|
|
||||||
var options = new ExtractionOptions
|
|
||||||
{
|
|
||||||
ExtractFullPath = true,
|
|
||||||
Overwrite = overwriteExistingFiles
|
|
||||||
};
|
|
||||||
|
|
||||||
Directory.CreateDirectory(targetPath);
|
|
||||||
reader.WriteAllToDirectory(targetPath, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void ExtractAllFromGz(Stream source, string targetPath, bool overwriteExistingFiles)
|
public void ExtractAllFromGz(Stream source, string targetPath, bool overwriteExistingFiles)
|
||||||
{
|
{
|
||||||
@@ -94,69 +42,5 @@ namespace Emby.Server.Implementations.Archiving
|
|||||||
reader.WriteEntryToFile(Path.Combine(targetPath, filename));
|
reader.WriteEntryToFile(Path.Combine(targetPath, filename));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Extracts all from7z.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="sourceFile">The source file.</param>
|
|
||||||
/// <param name="targetPath">The target path.</param>
|
|
||||||
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
|
|
||||||
public void ExtractAllFrom7z(string sourceFile, string targetPath, bool overwriteExistingFiles)
|
|
||||||
{
|
|
||||||
using var fileStream = File.OpenRead(sourceFile);
|
|
||||||
ExtractAllFrom7z(fileStream, targetPath, overwriteExistingFiles);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Extracts all from7z.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="source">The source.</param>
|
|
||||||
/// <param name="targetPath">The target path.</param>
|
|
||||||
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
|
|
||||||
public void ExtractAllFrom7z(Stream source, string targetPath, bool overwriteExistingFiles)
|
|
||||||
{
|
|
||||||
using var archive = SevenZipArchive.Open(source);
|
|
||||||
using var reader = archive.ExtractAllEntries();
|
|
||||||
var options = new ExtractionOptions
|
|
||||||
{
|
|
||||||
ExtractFullPath = true,
|
|
||||||
Overwrite = overwriteExistingFiles
|
|
||||||
};
|
|
||||||
|
|
||||||
Directory.CreateDirectory(targetPath);
|
|
||||||
reader.WriteAllToDirectory(targetPath, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Extracts all from tar.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="sourceFile">The source file.</param>
|
|
||||||
/// <param name="targetPath">The target path.</param>
|
|
||||||
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
|
|
||||||
public void ExtractAllFromTar(string sourceFile, string targetPath, bool overwriteExistingFiles)
|
|
||||||
{
|
|
||||||
using var fileStream = File.OpenRead(sourceFile);
|
|
||||||
ExtractAllFromTar(fileStream, targetPath, overwriteExistingFiles);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Extracts all from tar.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="source">The source.</param>
|
|
||||||
/// <param name="targetPath">The target path.</param>
|
|
||||||
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
|
|
||||||
public void ExtractAllFromTar(Stream source, string targetPath, bool overwriteExistingFiles)
|
|
||||||
{
|
|
||||||
using var archive = TarArchive.Open(source);
|
|
||||||
using var reader = archive.ExtractAllEntries();
|
|
||||||
var options = new ExtractionOptions
|
|
||||||
{
|
|
||||||
ExtractFullPath = true,
|
|
||||||
Overwrite = overwriteExistingFiles
|
|
||||||
};
|
|
||||||
|
|
||||||
Directory.CreateDirectory(targetPath);
|
|
||||||
reader.WriteAllToDirectory(targetPath, options);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ using System.Threading;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Jellyfin.Data.Entities;
|
using Jellyfin.Data.Entities;
|
||||||
using Jellyfin.Data.Enums;
|
using Jellyfin.Data.Enums;
|
||||||
|
using Jellyfin.Extensions;
|
||||||
using Jellyfin.Extensions.Json;
|
using Jellyfin.Extensions.Json;
|
||||||
using MediaBrowser.Common.Extensions;
|
using MediaBrowser.Common.Extensions;
|
||||||
using MediaBrowser.Common.Progress;
|
using MediaBrowser.Common.Progress;
|
||||||
@@ -38,7 +39,7 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The LiveTV channel manager.
|
/// The LiveTV channel manager.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ChannelManager : IChannelManager
|
public class ChannelManager : IChannelManager, IDisposable
|
||||||
{
|
{
|
||||||
private readonly IUserManager _userManager;
|
private readonly IUserManager _userManager;
|
||||||
private readonly IUserDataManager _userDataManager;
|
private readonly IUserDataManager _userDataManager;
|
||||||
@@ -51,6 +52,7 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
private readonly IMemoryCache _memoryCache;
|
private readonly IMemoryCache _memoryCache;
|
||||||
private readonly SemaphoreSlim _resourcePool = new SemaphoreSlim(1, 1);
|
private readonly SemaphoreSlim _resourcePool = new SemaphoreSlim(1, 1);
|
||||||
private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
|
private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
|
||||||
|
private bool _disposed = false;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="ChannelManager"/> class.
|
/// Initializes a new instance of the <see cref="ChannelManager"/> class.
|
||||||
@@ -129,16 +131,14 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
var internalChannel = _libraryManager.GetItemById(item.ChannelId);
|
var internalChannel = _libraryManager.GetItemById(item.ChannelId);
|
||||||
if (internalChannel == null)
|
if (internalChannel == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentException();
|
throw new ArgumentException(nameof(item.ChannelId));
|
||||||
}
|
}
|
||||||
|
|
||||||
var channel = Channels.FirstOrDefault(i => GetInternalChannelId(i.Name).Equals(internalChannel.Id));
|
var channel = Channels.FirstOrDefault(i => GetInternalChannelId(i.Name).Equals(internalChannel.Id));
|
||||||
|
|
||||||
var supportsDelete = channel as ISupportsDelete;
|
if (channel is not ISupportsDelete supportsDelete)
|
||||||
|
|
||||||
if (supportsDelete == null)
|
|
||||||
{
|
{
|
||||||
throw new ArgumentException();
|
throw new ArgumentException(nameof(channel));
|
||||||
}
|
}
|
||||||
|
|
||||||
return supportsDelete.DeleteItem(item.ExternalId, CancellationToken.None);
|
return supportsDelete.DeleteItem(item.ExternalId, CancellationToken.None);
|
||||||
@@ -162,7 +162,7 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public QueryResult<Channel> GetChannelsInternal(ChannelQuery query)
|
public QueryResult<Channel> GetChannelsInternal(ChannelQuery query)
|
||||||
{
|
{
|
||||||
var user = query.UserId.Equals(Guid.Empty)
|
var user = query.UserId.Equals(default)
|
||||||
? null
|
? null
|
||||||
: _userManager.GetUserById(query.UserId);
|
: _userManager.GetUserById(query.UserId);
|
||||||
|
|
||||||
@@ -179,7 +179,7 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
return (GetChannelProvider(i) is IHasFolderAttributes hasAttributes
|
return (GetChannelProvider(i) is IHasFolderAttributes hasAttributes
|
||||||
&& hasAttributes.Attributes.Contains("Recordings", StringComparer.OrdinalIgnoreCase)) == val;
|
&& hasAttributes.Attributes.Contains("Recordings", StringComparison.OrdinalIgnoreCase)) == val;
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@@ -265,17 +265,16 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new QueryResult<Channel>
|
return new QueryResult<Channel>(
|
||||||
{
|
query.StartIndex,
|
||||||
Items = all,
|
totalCount,
|
||||||
TotalRecordCount = totalCount
|
all);
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public QueryResult<BaseItemDto> GetChannels(ChannelQuery query)
|
public QueryResult<BaseItemDto> GetChannels(ChannelQuery query)
|
||||||
{
|
{
|
||||||
var user = query.UserId.Equals(Guid.Empty)
|
var user = query.UserId.Equals(default)
|
||||||
? null
|
? null
|
||||||
: _userManager.GetUserById(query.UserId);
|
: _userManager.GetUserById(query.UserId);
|
||||||
|
|
||||||
@@ -286,11 +285,10 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
// TODO Fix The co-variant conversion (internalResult.Items) between Folder[] and BaseItem[], this can generate runtime issues.
|
// TODO Fix The co-variant conversion (internalResult.Items) between Folder[] and BaseItem[], this can generate runtime issues.
|
||||||
var returnItems = _dtoService.GetBaseItemDtos(internalResult.Items, dtoOptions, user);
|
var returnItems = _dtoService.GetBaseItemDtos(internalResult.Items, dtoOptions, user);
|
||||||
|
|
||||||
var result = new QueryResult<BaseItemDto>
|
var result = new QueryResult<BaseItemDto>(
|
||||||
{
|
query.StartIndex,
|
||||||
Items = returnItems,
|
internalResult.TotalRecordCount,
|
||||||
TotalRecordCount = internalResult.TotalRecordCount
|
returnItems);
|
||||||
};
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -334,7 +332,7 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
|
|
||||||
private Channel GetChannelEntity(IChannel channel)
|
private Channel GetChannelEntity(IChannel channel)
|
||||||
{
|
{
|
||||||
return GetChannel(GetInternalChannelId(channel.Name)) ?? GetChannel(channel, CancellationToken.None).Result;
|
return GetChannel(GetInternalChannelId(channel.Name)) ?? GetChannel(channel, CancellationToken.None).GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
private MediaSourceInfo[] GetSavedMediaSources(BaseItem item)
|
private MediaSourceInfo[] GetSavedMediaSources(BaseItem item)
|
||||||
@@ -476,7 +474,7 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
|
|
||||||
item.ChannelId = id;
|
item.ChannelId = id;
|
||||||
|
|
||||||
if (item.ParentId != parentFolderId)
|
if (!item.ParentId.Equals(parentFolderId))
|
||||||
{
|
{
|
||||||
forceUpdate = true;
|
forceUpdate = true;
|
||||||
}
|
}
|
||||||
@@ -541,7 +539,7 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
return _libraryManager.GetItemIds(
|
return _libraryManager.GetItemIds(
|
||||||
new InternalItemsQuery
|
new InternalItemsQuery
|
||||||
{
|
{
|
||||||
IncludeItemTypes = new[] { nameof(Channel) },
|
IncludeItemTypes = new[] { BaseItemKind.Channel },
|
||||||
OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) }
|
OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) }
|
||||||
}).Select(i => GetChannelFeatures(i)).ToArray();
|
}).Select(i => GetChannelFeatures(i)).ToArray();
|
||||||
}
|
}
|
||||||
@@ -621,11 +619,10 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
|
|
||||||
var returnItems = _dtoService.GetBaseItemDtos(items, query.DtoOptions, query.User);
|
var returnItems = _dtoService.GetBaseItemDtos(items, query.DtoOptions, query.User);
|
||||||
|
|
||||||
var result = new QueryResult<BaseItemDto>
|
var result = new QueryResult<BaseItemDto>(
|
||||||
{
|
query.StartIndex,
|
||||||
Items = returnItems,
|
totalRecordCount,
|
||||||
TotalRecordCount = totalRecordCount
|
returnItems);
|
||||||
};
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -718,7 +715,9 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
// Find the corresponding channel provider plugin
|
// Find the corresponding channel provider plugin
|
||||||
var channelProvider = GetChannelProvider(channel);
|
var channelProvider = GetChannelProvider(channel);
|
||||||
|
|
||||||
var parentItem = query.ParentId == Guid.Empty ? channel : _libraryManager.GetItemById(query.ParentId);
|
var parentItem = query.ParentId.Equals(default)
|
||||||
|
? channel
|
||||||
|
: _libraryManager.GetItemById(query.ParentId);
|
||||||
|
|
||||||
var itemsResult = await GetChannelItems(
|
var itemsResult = await GetChannelItems(
|
||||||
channelProvider,
|
channelProvider,
|
||||||
@@ -729,7 +728,7 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
cancellationToken)
|
cancellationToken)
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
if (query.ParentId == Guid.Empty)
|
if (query.ParentId.Equals(default))
|
||||||
{
|
{
|
||||||
query.Parent = channel;
|
query.Parent = channel;
|
||||||
}
|
}
|
||||||
@@ -787,11 +786,10 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
|
|
||||||
var returnItems = _dtoService.GetBaseItemDtos(internalResult.Items, query.DtoOptions, query.User);
|
var returnItems = _dtoService.GetBaseItemDtos(internalResult.Items, query.DtoOptions, query.User);
|
||||||
|
|
||||||
var result = new QueryResult<BaseItemDto>
|
var result = new QueryResult<BaseItemDto>(
|
||||||
{
|
query.StartIndex,
|
||||||
Items = returnItems,
|
internalResult.TotalRecordCount,
|
||||||
TotalRecordCount = internalResult.TotalRecordCount
|
returnItems);
|
||||||
};
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -1075,14 +1073,6 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
forceUpdate = true;
|
forceUpdate = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// was used for status
|
|
||||||
// if (!string.Equals(item.ExternalEtag ?? string.Empty, info.Etag ?? string.Empty, StringComparison.Ordinal))
|
|
||||||
// {
|
|
||||||
// item.ExternalEtag = info.Etag;
|
|
||||||
// forceUpdate = true;
|
|
||||||
// _logger.LogDebug("Forcing update due to ExternalEtag {0}", item.Name);
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (!internalChannelId.Equals(item.ChannelId))
|
if (!internalChannelId.Equals(item.ChannelId))
|
||||||
{
|
{
|
||||||
forceUpdate = true;
|
forceUpdate = true;
|
||||||
@@ -1143,7 +1133,7 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
|
|
||||||
if (!info.IsLiveStream)
|
if (!info.IsLiveStream)
|
||||||
{
|
{
|
||||||
if (item.Tags.Contains("livestream", StringComparer.OrdinalIgnoreCase))
|
if (item.Tags.Contains("livestream", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
item.Tags = item.Tags.Except(new[] { "livestream" }, StringComparer.OrdinalIgnoreCase).ToArray();
|
item.Tags = item.Tags.Except(new[] { "livestream" }, StringComparer.OrdinalIgnoreCase).ToArray();
|
||||||
_logger.LogDebug("Forcing update due to Tags {0}", item.Name);
|
_logger.LogDebug("Forcing update due to Tags {0}", item.Name);
|
||||||
@@ -1152,7 +1142,7 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (!item.Tags.Contains("livestream", StringComparer.OrdinalIgnoreCase))
|
if (!item.Tags.Contains("livestream", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
item.Tags = item.Tags.Concat(new[] { "livestream" }).ToArray();
|
item.Tags = item.Tags.Concat(new[] { "livestream" }).ToArray();
|
||||||
_logger.LogDebug("Forcing update due to Tags {0}", item.Name);
|
_logger.LogDebug("Forcing update due to Tags {0}", item.Name);
|
||||||
@@ -1226,5 +1216,31 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Releases unmanaged and optionally managed resources.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
|
||||||
|
protected virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (_disposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
_resourcePool?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
_disposed = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using System;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Jellyfin.Data.Enums;
|
||||||
using MediaBrowser.Controller.Channels;
|
using MediaBrowser.Controller.Channels;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
@@ -51,7 +52,7 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
|
|
||||||
var uninstalledChannels = _libraryManager.GetItemList(new InternalItemsQuery
|
var uninstalledChannels = _libraryManager.GetItemList(new InternalItemsQuery
|
||||||
{
|
{
|
||||||
IncludeItemTypes = new[] { nameof(Channel) },
|
IncludeItemTypes = new[] { BaseItemKind.Channel },
|
||||||
ExcludeItemIds = installedChannelIds.ToArray()
|
ExcludeItemIds = installedChannelIds.ToArray()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
public string Key => "RefreshInternetChannels";
|
public string Key => "RefreshInternetChannels";
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task Execute(CancellationToken cancellationToken, IProgress<double> progress)
|
public async Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var manager = (ChannelManager)_channelManager;
|
var manager = (ChannelManager)_channelManager;
|
||||||
|
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ namespace Emby.Server.Implementations.Collections
|
|||||||
|
|
||||||
if (parentFolder == null)
|
if (parentFolder == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentException();
|
throw new ArgumentException(nameof(parentFolder));
|
||||||
}
|
}
|
||||||
|
|
||||||
var path = Path.Combine(parentFolder.Path, folderName);
|
var path = Path.Combine(parentFolder.Path, folderName);
|
||||||
@@ -265,7 +265,7 @@ namespace Emby.Server.Implementations.Collections
|
|||||||
{
|
{
|
||||||
var childItem = _libraryManager.GetItemById(guidId);
|
var childItem = _libraryManager.GetItemById(guidId);
|
||||||
|
|
||||||
var child = collection.LinkedChildren.FirstOrDefault(i => (i.ItemId.HasValue && i.ItemId.Value == guidId) || (childItem != null && string.Equals(childItem.Path, i.Path, StringComparison.OrdinalIgnoreCase)));
|
var child = collection.LinkedChildren.FirstOrDefault(i => (i.ItemId.HasValue && i.ItemId.Value.Equals(guidId)) || (childItem != null && string.Equals(childItem.Path, i.Path, StringComparison.OrdinalIgnoreCase)));
|
||||||
|
|
||||||
if (child == null)
|
if (child == null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
using MediaBrowser.Common.Extensions;
|
using MediaBrowser.Common.Extensions;
|
||||||
using MediaBrowser.Model.Cryptography;
|
using MediaBrowser.Model.Cryptography;
|
||||||
using static MediaBrowser.Common.Cryptography.Constants;
|
using static MediaBrowser.Model.Cryptography.Constants;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Cryptography
|
namespace Emby.Server.Implementations.Cryptography
|
||||||
{
|
{
|
||||||
@@ -12,10 +14,7 @@ namespace Emby.Server.Implementations.Cryptography
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class CryptographyProvider : ICryptoProvider
|
public class CryptographyProvider : ICryptoProvider
|
||||||
{
|
{
|
||||||
// FIXME: When we get DotNet Standard 2.1 we need to revisit how we do the crypto
|
// TODO: remove when not needed for backwards compat
|
||||||
// Currently supported hash methods from https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptoconfig?view=netcore-2.1
|
|
||||||
// there might be a better way to autogenerate this list as dotnet updates, but I couldn't find one
|
|
||||||
// Please note the default method of PBKDF2 is not included, it cannot be used to generate hashes cleanly as it is actually a pbkdf with sha1
|
|
||||||
private static readonly HashSet<string> _supportedHashMethods = new HashSet<string>()
|
private static readonly HashSet<string> _supportedHashMethods = new HashSet<string>()
|
||||||
{
|
{
|
||||||
"MD5",
|
"MD5",
|
||||||
@@ -35,60 +34,81 @@ namespace Emby.Server.Implementations.Cryptography
|
|||||||
};
|
};
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public string DefaultHashMethod => "PBKDF2";
|
public string DefaultHashMethod => "PBKDF2-SHA512";
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public IEnumerable<string> GetSupportedHashMethods()
|
public PasswordHash CreatePasswordHash(ReadOnlySpan<char> password)
|
||||||
=> _supportedHashMethods;
|
|
||||||
|
|
||||||
private byte[] PBKDF2(string method, byte[] bytes, byte[] salt, int iterations)
|
|
||||||
{
|
{
|
||||||
// downgrading for now as we need this library to be dotnetstandard compliant
|
byte[] salt = GenerateSalt();
|
||||||
// with this downgrade we'll add a check to make sure we're on the downgrade method at the moment
|
return new PasswordHash(
|
||||||
if (method != DefaultHashMethod)
|
DefaultHashMethod,
|
||||||
{
|
Rfc2898DeriveBytes.Pbkdf2(
|
||||||
throw new CryptographicException($"Cannot currently use PBKDF2 with requested hash method: {method}");
|
password,
|
||||||
}
|
salt,
|
||||||
|
DefaultIterations,
|
||||||
using var r = new Rfc2898DeriveBytes(bytes, salt, iterations);
|
HashAlgorithmName.SHA512,
|
||||||
return r.GetBytes(32);
|
DefaultOutputLength),
|
||||||
|
salt,
|
||||||
|
new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "iterations", DefaultIterations.ToString(CultureInfo.InvariantCulture) }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public byte[] ComputeHash(string hashMethod, byte[] bytes, byte[] salt)
|
public bool Verify(PasswordHash hash, ReadOnlySpan<char> password)
|
||||||
{
|
{
|
||||||
if (hashMethod == DefaultHashMethod)
|
if (string.Equals(hash.Id, "PBKDF2", StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
return PBKDF2(hashMethod, bytes, salt, DefaultIterations);
|
return hash.Hash.SequenceEqual(
|
||||||
|
Rfc2898DeriveBytes.Pbkdf2(
|
||||||
|
password,
|
||||||
|
hash.Salt,
|
||||||
|
int.Parse(hash.Parameters["iterations"], CultureInfo.InvariantCulture),
|
||||||
|
HashAlgorithmName.SHA1,
|
||||||
|
32));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_supportedHashMethods.Contains(hashMethod))
|
if (string.Equals(hash.Id, "PBKDF2-SHA512", StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
throw new CryptographicException($"Requested hash method is not supported: {hashMethod}");
|
return hash.Hash.SequenceEqual(
|
||||||
|
Rfc2898DeriveBytes.Pbkdf2(
|
||||||
|
password,
|
||||||
|
hash.Salt,
|
||||||
|
int.Parse(hash.Parameters["iterations"], CultureInfo.InvariantCulture),
|
||||||
|
HashAlgorithmName.SHA512,
|
||||||
|
DefaultOutputLength));
|
||||||
}
|
}
|
||||||
|
|
||||||
using var h = HashAlgorithm.Create(hashMethod) ?? throw new ResourceNotFoundException($"Unknown hash method: {hashMethod}.");
|
if (!_supportedHashMethods.Contains(hash.Id))
|
||||||
if (salt.Length == 0)
|
|
||||||
{
|
{
|
||||||
return h.ComputeHash(bytes);
|
throw new CryptographicException($"Requested hash method is not supported: {hash.Id}");
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] salted = new byte[bytes.Length + salt.Length];
|
using var h = HashAlgorithm.Create(hash.Id) ?? throw new ResourceNotFoundException($"Unknown hash method: {hash.Id}.");
|
||||||
|
var bytes = Encoding.UTF8.GetBytes(password.ToArray());
|
||||||
|
if (hash.Salt.Length == 0)
|
||||||
|
{
|
||||||
|
return hash.Hash.SequenceEqual(h.ComputeHash(bytes));
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] salted = new byte[bytes.Length + hash.Salt.Length];
|
||||||
Array.Copy(bytes, salted, bytes.Length);
|
Array.Copy(bytes, salted, bytes.Length);
|
||||||
Array.Copy(salt, 0, salted, bytes.Length, salt.Length);
|
hash.Salt.CopyTo(salted.AsSpan(bytes.Length));
|
||||||
return h.ComputeHash(salted);
|
return hash.Hash.SequenceEqual(h.ComputeHash(salted));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public byte[] ComputeHashWithDefaultMethod(byte[] bytes, byte[] salt)
|
|
||||||
=> PBKDF2(DefaultHashMethod, bytes, salt, DefaultIterations);
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public byte[] GenerateSalt()
|
public byte[] GenerateSalt()
|
||||||
=> GenerateSalt(DefaultSaltLength);
|
=> GenerateSalt(DefaultSaltLength);
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public byte[] GenerateSalt(int length)
|
public byte[] GenerateSalt(int length)
|
||||||
=> RandomNumberGenerator.GetBytes(length);
|
{
|
||||||
|
var salt = new byte[length];
|
||||||
|
using var rng = RandomNumberGenerator.Create();
|
||||||
|
rng.GetNonZeroBytes(salt);
|
||||||
|
return salt;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using Jellyfin.Extensions;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using SQLitePCL.pretty;
|
using SQLitePCL.pretty;
|
||||||
|
|
||||||
@@ -160,21 +160,22 @@ namespace Emby.Server.Implementations.Data
|
|||||||
protected bool TableExists(ManagedConnection connection, string name)
|
protected bool TableExists(ManagedConnection connection, string name)
|
||||||
{
|
{
|
||||||
return connection.RunInTransaction(
|
return connection.RunInTransaction(
|
||||||
db =>
|
db =>
|
||||||
{
|
|
||||||
using (var statement = PrepareStatement(db, "select DISTINCT tbl_name from sqlite_master"))
|
|
||||||
{
|
{
|
||||||
foreach (var row in statement.ExecuteQuery())
|
using (var statement = PrepareStatement(db, "select DISTINCT tbl_name from sqlite_master"))
|
||||||
{
|
{
|
||||||
if (string.Equals(name, row.GetString(0), StringComparison.OrdinalIgnoreCase))
|
foreach (var row in statement.ExecuteQuery())
|
||||||
{
|
{
|
||||||
return true;
|
if (string.Equals(name, row.GetString(0), StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}, ReadTransactionMode);
|
},
|
||||||
|
ReadTransactionMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected List<string> GetColumnNames(IDatabaseConnection connection, string table)
|
protected List<string> GetColumnNames(IDatabaseConnection connection, string table)
|
||||||
@@ -194,7 +195,7 @@ namespace Emby.Server.Implementations.Data
|
|||||||
|
|
||||||
protected void AddColumn(IDatabaseConnection connection, string table, string columnName, string type, List<string> existingColumnNames)
|
protected void AddColumn(IDatabaseConnection connection, string table, string columnName, string type, List<string> existingColumnNames)
|
||||||
{
|
{
|
||||||
if (existingColumnNames.Contains(columnName, StringComparer.OrdinalIgnoreCase))
|
if (existingColumnNames.Contains(columnName, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ using SQLitePCL.pretty;
|
|||||||
|
|
||||||
namespace Emby.Server.Implementations.Data
|
namespace Emby.Server.Implementations.Data
|
||||||
{
|
{
|
||||||
public class ManagedConnection : IDisposable
|
public sealed class ManagedConnection : IDisposable
|
||||||
{
|
{
|
||||||
private readonly SemaphoreSlim _writeLock;
|
private readonly SemaphoreSlim _writeLock;
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -26,9 +26,6 @@ namespace Emby.Server.Implementations.Data
|
|||||||
DbFilePath = Path.Combine(appPaths.DataPath, "library.db");
|
DbFilePath = Path.Combine(appPaths.DataPath, "library.db");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public string Name => "SQLite";
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Opens the connection to the database.
|
/// Opens the connection to the database.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -50,41 +47,42 @@ namespace Emby.Server.Implementations.Data
|
|||||||
var users = userDatasTableExists ? null : userManager.Users;
|
var users = userDatasTableExists ? null : userManager.Users;
|
||||||
|
|
||||||
connection.RunInTransaction(
|
connection.RunInTransaction(
|
||||||
db =>
|
db =>
|
||||||
{
|
|
||||||
db.ExecuteAll(string.Join(';', new[]
|
|
||||||
{
|
{
|
||||||
"create table if not exists UserDatas (key nvarchar not null, userId INT not null, rating float null, played bit not null, playCount int not null, isFavorite bit not null, playbackPositionTicks bigint not null, lastPlayedDate datetime null, AudioStreamIndex INT, SubtitleStreamIndex INT)",
|
db.ExecuteAll(string.Join(';', new[]
|
||||||
|
|
||||||
"drop index if exists idx_userdata",
|
|
||||||
"drop index if exists idx_userdata1",
|
|
||||||
"drop index if exists idx_userdata2",
|
|
||||||
"drop index if exists userdataindex1",
|
|
||||||
"drop index if exists userdataindex",
|
|
||||||
"drop index if exists userdataindex3",
|
|
||||||
"drop index if exists userdataindex4",
|
|
||||||
"create unique index if not exists UserDatasIndex1 on UserDatas (key, userId)",
|
|
||||||
"create index if not exists UserDatasIndex2 on UserDatas (key, userId, played)",
|
|
||||||
"create index if not exists UserDatasIndex3 on UserDatas (key, userId, playbackPositionTicks)",
|
|
||||||
"create index if not exists UserDatasIndex4 on UserDatas (key, userId, isFavorite)"
|
|
||||||
}));
|
|
||||||
|
|
||||||
if (userDataTableExists)
|
|
||||||
{
|
|
||||||
var existingColumnNames = GetColumnNames(db, "userdata");
|
|
||||||
|
|
||||||
AddColumn(db, "userdata", "InternalUserId", "int", existingColumnNames);
|
|
||||||
AddColumn(db, "userdata", "AudioStreamIndex", "int", existingColumnNames);
|
|
||||||
AddColumn(db, "userdata", "SubtitleStreamIndex", "int", existingColumnNames);
|
|
||||||
|
|
||||||
if (!userDatasTableExists)
|
|
||||||
{
|
{
|
||||||
ImportUserIds(db, users);
|
"create table if not exists UserDatas (key nvarchar not null, userId INT not null, rating float null, played bit not null, playCount int not null, isFavorite bit not null, playbackPositionTicks bigint not null, lastPlayedDate datetime null, AudioStreamIndex INT, SubtitleStreamIndex INT)",
|
||||||
|
|
||||||
db.ExecuteAll("INSERT INTO UserDatas (key, userId, rating, played, playCount, isFavorite, playbackPositionTicks, lastPlayedDate, AudioStreamIndex, SubtitleStreamIndex) SELECT key, InternalUserId, rating, played, playCount, isFavorite, playbackPositionTicks, lastPlayedDate, AudioStreamIndex, SubtitleStreamIndex from userdata where InternalUserId not null");
|
"drop index if exists idx_userdata",
|
||||||
|
"drop index if exists idx_userdata1",
|
||||||
|
"drop index if exists idx_userdata2",
|
||||||
|
"drop index if exists userdataindex1",
|
||||||
|
"drop index if exists userdataindex",
|
||||||
|
"drop index if exists userdataindex3",
|
||||||
|
"drop index if exists userdataindex4",
|
||||||
|
"create unique index if not exists UserDatasIndex1 on UserDatas (key, userId)",
|
||||||
|
"create index if not exists UserDatasIndex2 on UserDatas (key, userId, played)",
|
||||||
|
"create index if not exists UserDatasIndex3 on UserDatas (key, userId, playbackPositionTicks)",
|
||||||
|
"create index if not exists UserDatasIndex4 on UserDatas (key, userId, isFavorite)"
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (userDataTableExists)
|
||||||
|
{
|
||||||
|
var existingColumnNames = GetColumnNames(db, "userdata");
|
||||||
|
|
||||||
|
AddColumn(db, "userdata", "InternalUserId", "int", existingColumnNames);
|
||||||
|
AddColumn(db, "userdata", "AudioStreamIndex", "int", existingColumnNames);
|
||||||
|
AddColumn(db, "userdata", "SubtitleStreamIndex", "int", existingColumnNames);
|
||||||
|
|
||||||
|
if (!userDatasTableExists)
|
||||||
|
{
|
||||||
|
ImportUserIds(db, users);
|
||||||
|
|
||||||
|
db.ExecuteAll("INSERT INTO UserDatas (key, userId, rating, played, playCount, isFavorite, playbackPositionTicks, lastPlayedDate, AudioStreamIndex, SubtitleStreamIndex) SELECT key, InternalUserId, rating, played, playCount, isFavorite, playbackPositionTicks, lastPlayedDate, AudioStreamIndex, SubtitleStreamIndex from userdata where InternalUserId not null");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}, TransactionMode);
|
TransactionMode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,7 +99,7 @@ namespace Emby.Server.Implementations.Data
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
statement.TryBind("@UserId", user.Id.ToByteArray());
|
statement.TryBind("@UserId", user.Id);
|
||||||
statement.TryBind("@InternalUserId", user.InternalId);
|
statement.TryBind("@InternalUserId", user.InternalId);
|
||||||
|
|
||||||
statement.MoveNext();
|
statement.MoveNext();
|
||||||
@@ -183,10 +181,11 @@ namespace Emby.Server.Implementations.Data
|
|||||||
using (var connection = GetConnection())
|
using (var connection = GetConnection())
|
||||||
{
|
{
|
||||||
connection.RunInTransaction(
|
connection.RunInTransaction(
|
||||||
db =>
|
db =>
|
||||||
{
|
{
|
||||||
SaveUserData(db, internalUserId, key, userData);
|
SaveUserData(db, internalUserId, key, userData);
|
||||||
}, TransactionMode);
|
},
|
||||||
|
TransactionMode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -252,13 +251,14 @@ namespace Emby.Server.Implementations.Data
|
|||||||
using (var connection = GetConnection())
|
using (var connection = GetConnection())
|
||||||
{
|
{
|
||||||
connection.RunInTransaction(
|
connection.RunInTransaction(
|
||||||
db =>
|
db =>
|
||||||
{
|
|
||||||
foreach (var userItemData in userDataList)
|
|
||||||
{
|
{
|
||||||
SaveUserData(db, internalUserId, userItemData.Key, userItemData);
|
foreach (var userItemData in userDataList)
|
||||||
}
|
{
|
||||||
}, TransactionMode);
|
SaveUserData(db, internalUserId, userItemData.Key, userItemData);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
TransactionMode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -387,6 +387,7 @@ namespace Emby.Server.Implementations.Data
|
|||||||
return userData;
|
return userData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#pragma warning disable CA2215
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// There is nothing to dispose here since <see cref="BaseSqliteRepository.WriteLock"/> and
|
/// There is nothing to dispose here since <see cref="BaseSqliteRepository.WriteLock"/> and
|
||||||
@@ -395,6 +396,10 @@ namespace Emby.Server.Implementations.Data
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
protected override void Dispose(bool dispose)
|
protected override void Dispose(bool dispose)
|
||||||
{
|
{
|
||||||
|
// The write lock and connection for the item repository are shared with the user data repository
|
||||||
|
// since they point to the same database. The item repo has responsibility for disposing these two objects,
|
||||||
|
// so the user data repo should not attempt to dispose them as well
|
||||||
}
|
}
|
||||||
|
#pragma warning restore CA2215
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ namespace Emby.Server.Implementations.Devices
|
|||||||
{
|
{
|
||||||
var value = File.ReadAllText(CachePath, Encoding.UTF8);
|
var value = File.ReadAllText(CachePath, Encoding.UTF8);
|
||||||
|
|
||||||
if (Guid.TryParse(value, out var guid))
|
if (Guid.TryParse(value, out _))
|
||||||
{
|
{
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ using System.Collections.Generic;
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Jellyfin.Data.Entities;
|
using Jellyfin.Data.Entities;
|
||||||
using Jellyfin.Data.Enums;
|
using Jellyfin.Data.Enums;
|
||||||
|
using Jellyfin.Extensions;
|
||||||
using MediaBrowser.Common;
|
using MediaBrowser.Common;
|
||||||
using MediaBrowser.Controller.Channels;
|
using MediaBrowser.Controller.Channels;
|
||||||
using MediaBrowser.Controller.Drawing;
|
using MediaBrowser.Controller.Drawing;
|
||||||
@@ -21,7 +21,6 @@ using MediaBrowser.Controller.LiveTv;
|
|||||||
using MediaBrowser.Controller.Persistence;
|
using MediaBrowser.Controller.Persistence;
|
||||||
using MediaBrowser.Controller.Playlists;
|
using MediaBrowser.Controller.Playlists;
|
||||||
using MediaBrowser.Controller.Providers;
|
using MediaBrowser.Controller.Providers;
|
||||||
using MediaBrowser.Model.Drawing;
|
|
||||||
using MediaBrowser.Model.Dto;
|
using MediaBrowser.Model.Dto;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.Querying;
|
using MediaBrowser.Model.Querying;
|
||||||
@@ -109,7 +108,7 @@ namespace Emby.Server.Implementations.Dto
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
SetItemByNameInfo(item, dto, libraryItems, user);
|
SetItemByNameInfo(item, dto, libraryItems);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,8 +152,7 @@ namespace Emby.Server.Implementations.Dto
|
|||||||
new DtoOptions(false)
|
new DtoOptions(false)
|
||||||
{
|
{
|
||||||
EnableImages = false
|
EnableImages = false
|
||||||
}),
|
}));
|
||||||
user);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return dto;
|
return dto;
|
||||||
@@ -294,7 +292,7 @@ namespace Emby.Server.Implementations.Dto
|
|||||||
path = path.TrimStart('.');
|
path = path.TrimStart('.');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(path) && containers.Contains(path, StringComparer.OrdinalIgnoreCase))
|
if (!string.IsNullOrEmpty(path) && containers.Contains(path, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
fileExtensionContainer = path;
|
fileExtensionContainer = path;
|
||||||
}
|
}
|
||||||
@@ -311,13 +309,13 @@ namespace Emby.Server.Implementations.Dto
|
|||||||
|
|
||||||
if (taggedItems != null && options.ContainsField(ItemFields.ItemCounts))
|
if (taggedItems != null && options.ContainsField(ItemFields.ItemCounts))
|
||||||
{
|
{
|
||||||
SetItemByNameInfo(item, dto, taggedItems, user);
|
SetItemByNameInfo(item, dto, taggedItems);
|
||||||
}
|
}
|
||||||
|
|
||||||
return dto;
|
return dto;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void SetItemByNameInfo(BaseItem item, BaseItemDto dto, IList<BaseItem> taggedItems, User user = null)
|
private static void SetItemByNameInfo(BaseItem item, BaseItemDto dto, IList<BaseItem> taggedItems)
|
||||||
{
|
{
|
||||||
if (item is MusicArtist)
|
if (item is MusicArtist)
|
||||||
{
|
{
|
||||||
@@ -370,6 +368,12 @@ namespace Emby.Server.Implementations.Dto
|
|||||||
if (item is MusicAlbum || item is Season || item is Playlist)
|
if (item is MusicAlbum || item is Season || item is Playlist)
|
||||||
{
|
{
|
||||||
dto.ChildCount = dto.RecursiveItemCount;
|
dto.ChildCount = dto.RecursiveItemCount;
|
||||||
|
var folderChildCount = folder.LinkedChildren.Length;
|
||||||
|
// The default is an empty array, so we can't reliably use the count when it's empty
|
||||||
|
if (folderChildCount > 0)
|
||||||
|
{
|
||||||
|
dto.ChildCount ??= folderChildCount;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.ContainsField(ItemFields.ChildCount))
|
if (options.ContainsField(ItemFields.ChildCount))
|
||||||
@@ -453,18 +457,13 @@ namespace Emby.Server.Implementations.Dto
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetDtoId(BaseItem item)
|
|
||||||
{
|
|
||||||
return item.Id.ToString("N", CultureInfo.InvariantCulture);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SetMusicVideoProperties(BaseItemDto dto, MusicVideo item)
|
private void SetMusicVideoProperties(BaseItemDto dto, MusicVideo item)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(item.Album))
|
if (!string.IsNullOrEmpty(item.Album))
|
||||||
{
|
{
|
||||||
var parentAlbumIds = _libraryManager.GetItemIds(new InternalItemsQuery
|
var parentAlbumIds = _libraryManager.GetItemIds(new InternalItemsQuery
|
||||||
{
|
{
|
||||||
IncludeItemTypes = new[] { nameof(MusicAlbum) },
|
IncludeItemTypes = new[] { BaseItemKind.MusicAlbum },
|
||||||
Name = item.Album,
|
Name = item.Album,
|
||||||
Limit = 1
|
Limit = 1
|
||||||
});
|
});
|
||||||
@@ -579,7 +578,7 @@ namespace Emby.Server.Implementations.Dto
|
|||||||
if (dictionary.TryGetValue(person.Name, out Person entity))
|
if (dictionary.TryGetValue(person.Name, out Person entity))
|
||||||
{
|
{
|
||||||
baseItemPerson.PrimaryImageTag = GetTagAndFillBlurhash(dto, entity, ImageType.Primary);
|
baseItemPerson.PrimaryImageTag = GetTagAndFillBlurhash(dto, entity, ImageType.Primary);
|
||||||
baseItemPerson.Id = entity.Id.ToString("N", CultureInfo.InvariantCulture);
|
baseItemPerson.Id = entity.Id;
|
||||||
if (dto.ImageBlurHashes != null)
|
if (dto.ImageBlurHashes != null)
|
||||||
{
|
{
|
||||||
// Only add BlurHash for the person's image.
|
// Only add BlurHash for the person's image.
|
||||||
@@ -738,8 +737,7 @@ namespace Emby.Server.Implementations.Dto
|
|||||||
dto.Tags = item.Tags;
|
dto.Tags = item.Tags;
|
||||||
}
|
}
|
||||||
|
|
||||||
var hasAspectRatio = item as IHasAspectRatio;
|
if (item is IHasAspectRatio hasAspectRatio)
|
||||||
if (hasAspectRatio != null)
|
|
||||||
{
|
{
|
||||||
dto.AspectRatio = hasAspectRatio.AspectRatio;
|
dto.AspectRatio = hasAspectRatio.AspectRatio;
|
||||||
}
|
}
|
||||||
@@ -889,15 +887,13 @@ namespace Emby.Server.Implementations.Dto
|
|||||||
dto.CommunityRating = item.CommunityRating;
|
dto.CommunityRating = item.CommunityRating;
|
||||||
}
|
}
|
||||||
|
|
||||||
var supportsPlaceHolders = item as ISupportsPlaceHolders;
|
if (item is ISupportsPlaceHolders supportsPlaceHolders && supportsPlaceHolders.IsPlaceHolder)
|
||||||
if (supportsPlaceHolders != null && supportsPlaceHolders.IsPlaceHolder)
|
|
||||||
{
|
{
|
||||||
dto.IsPlaceHolder = supportsPlaceHolders.IsPlaceHolder;
|
dto.IsPlaceHolder = supportsPlaceHolders.IsPlaceHolder;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add audio info
|
// Add audio info
|
||||||
var audio = item as Audio;
|
if (item is Audio audio)
|
||||||
if (audio != null)
|
|
||||||
{
|
{
|
||||||
dto.Album = audio.Album;
|
dto.Album = audio.Album;
|
||||||
if (audio.ExtraType.HasValue)
|
if (audio.ExtraType.HasValue)
|
||||||
@@ -970,8 +966,7 @@ namespace Emby.Server.Implementations.Dto
|
|||||||
}).Where(i => i != null).ToArray();
|
}).Where(i => i != null).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
var hasAlbumArtist = item as IHasAlbumArtist;
|
if (item is IHasAlbumArtist hasAlbumArtist)
|
||||||
if (hasAlbumArtist != null)
|
|
||||||
{
|
{
|
||||||
dto.AlbumArtist = hasAlbumArtist.AlbumArtists.FirstOrDefault();
|
dto.AlbumArtist = hasAlbumArtist.AlbumArtists.FirstOrDefault();
|
||||||
|
|
||||||
@@ -1097,12 +1092,13 @@ namespace Emby.Server.Implementations.Dto
|
|||||||
|
|
||||||
if (options.ContainsField(ItemFields.LocalTrailerCount))
|
if (options.ContainsField(ItemFields.LocalTrailerCount))
|
||||||
{
|
{
|
||||||
allExtras ??= item.GetExtras().ToArray();
|
|
||||||
dto.LocalTrailerCount = allExtras.Count(i => i.ExtraType == ExtraType.Trailer);
|
|
||||||
|
|
||||||
if (item is IHasTrailers hasTrailers)
|
if (item is IHasTrailers hasTrailers)
|
||||||
{
|
{
|
||||||
dto.LocalTrailerCount += hasTrailers.GetTrailerCount();
|
dto.LocalTrailerCount = hasTrailers.GetTrailerCount();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
dto.LocalTrailerCount = (allExtras ?? item.GetExtras()).Count(i => i.ExtraType == ExtraType.Trailer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1312,35 +1308,35 @@ namespace Emby.Server.Implementations.Dto
|
|||||||
|
|
||||||
var allImages = parent.ImageInfos;
|
var allImages = parent.ImageInfos;
|
||||||
|
|
||||||
if (logoLimit > 0 && !(imageTags != null && imageTags.ContainsKey(ImageType.Logo)) && dto.ParentLogoItemId == null)
|
if (logoLimit > 0 && !(imageTags != null && imageTags.ContainsKey(ImageType.Logo)) && dto.ParentLogoItemId is null)
|
||||||
{
|
{
|
||||||
var image = allImages.FirstOrDefault(i => i.Type == ImageType.Logo);
|
var image = allImages.FirstOrDefault(i => i.Type == ImageType.Logo);
|
||||||
|
|
||||||
if (image != null)
|
if (image != null)
|
||||||
{
|
{
|
||||||
dto.ParentLogoItemId = GetDtoId(parent);
|
dto.ParentLogoItemId = parent.Id;
|
||||||
dto.ParentLogoImageTag = GetTagAndFillBlurhash(dto, parent, image);
|
dto.ParentLogoImageTag = GetTagAndFillBlurhash(dto, parent, image);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (artLimit > 0 && !(imageTags != null && imageTags.ContainsKey(ImageType.Art)) && dto.ParentArtItemId == null)
|
if (artLimit > 0 && !(imageTags != null && imageTags.ContainsKey(ImageType.Art)) && dto.ParentArtItemId is null)
|
||||||
{
|
{
|
||||||
var image = allImages.FirstOrDefault(i => i.Type == ImageType.Art);
|
var image = allImages.FirstOrDefault(i => i.Type == ImageType.Art);
|
||||||
|
|
||||||
if (image != null)
|
if (image != null)
|
||||||
{
|
{
|
||||||
dto.ParentArtItemId = GetDtoId(parent);
|
dto.ParentArtItemId = parent.Id;
|
||||||
dto.ParentArtImageTag = GetTagAndFillBlurhash(dto, parent, image);
|
dto.ParentArtImageTag = GetTagAndFillBlurhash(dto, parent, image);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (thumbLimit > 0 && !(imageTags != null && imageTags.ContainsKey(ImageType.Thumb)) && (dto.ParentThumbItemId == null || parent is Series) && parent is not ICollectionFolder && parent is not UserView)
|
if (thumbLimit > 0 && !(imageTags != null && imageTags.ContainsKey(ImageType.Thumb)) && (dto.ParentThumbItemId is null || parent is Series) && parent is not ICollectionFolder && parent is not UserView)
|
||||||
{
|
{
|
||||||
var image = allImages.FirstOrDefault(i => i.Type == ImageType.Thumb);
|
var image = allImages.FirstOrDefault(i => i.Type == ImageType.Thumb);
|
||||||
|
|
||||||
if (image != null)
|
if (image != null)
|
||||||
{
|
{
|
||||||
dto.ParentThumbItemId = GetDtoId(parent);
|
dto.ParentThumbItemId = parent.Id;
|
||||||
dto.ParentThumbImageTag = GetTagAndFillBlurhash(dto, parent, image);
|
dto.ParentThumbImageTag = GetTagAndFillBlurhash(dto, parent, image);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1351,7 +1347,7 @@ namespace Emby.Server.Implementations.Dto
|
|||||||
|
|
||||||
if (images.Count > 0)
|
if (images.Count > 0)
|
||||||
{
|
{
|
||||||
dto.ParentBackdropItemId = GetDtoId(parent);
|
dto.ParentBackdropItemId = parent.Id;
|
||||||
dto.ParentBackdropImageTags = GetTagsAndFillBlurhashes(dto, parent, ImageType.Backdrop, images);
|
dto.ParentBackdropImageTags = GetTagsAndFillBlurhashes(dto, parent, ImageType.Backdrop, images);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1398,44 +1394,27 @@ namespace Emby.Server.Implementations.Dto
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageDimensions size;
|
|
||||||
|
|
||||||
var defaultAspectRatio = item.GetDefaultPrimaryImageAspectRatio();
|
|
||||||
|
|
||||||
if (defaultAspectRatio > 0)
|
|
||||||
{
|
|
||||||
return defaultAspectRatio;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!imageInfo.IsLocalFile)
|
if (!imageInfo.IsLocalFile)
|
||||||
{
|
{
|
||||||
return null;
|
return item.GetDefaultPrimaryImageAspectRatio();
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
size = _imageProcessor.GetImageDimensions(item, imageInfo);
|
var size = _imageProcessor.GetImageDimensions(item, imageInfo);
|
||||||
|
var width = size.Width;
|
||||||
if (size.Width <= 0 || size.Height <= 0)
|
var height = size.Height;
|
||||||
|
if (width > 0 && height > 0)
|
||||||
{
|
{
|
||||||
return null;
|
return (double)width / height;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Failed to determine primary image aspect ratio for {0}", imageInfo.Path);
|
_logger.LogError(ex, "Failed to determine primary image aspect ratio for {ImagePath}", imageInfo.Path);
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var width = size.Width;
|
return item.GetDefaultPrimaryImageAspectRatio();
|
||||||
var height = size.Height;
|
|
||||||
|
|
||||||
if (width <= 0 || height <= 0)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (double)width / height;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,15 +24,15 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="DiscUtils.Udf" Version="0.16.13" />
|
<PackageReference Include="DiscUtils.Udf" Version="0.16.13" />
|
||||||
<PackageReference Include="Jellyfin.XmlTv" Version="10.6.2" />
|
<PackageReference Include="Jellyfin.XmlTv" Version="10.8.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="6.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="6.0.1" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="6.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.0" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.4" />
|
||||||
<PackageReference Include="Mono.Nat" Version="3.0.2" />
|
<PackageReference Include="Mono.Nat" Version="3.0.2" />
|
||||||
<PackageReference Include="prometheus-net.DotNetRuntime" Version="4.2.2" />
|
<PackageReference Include="prometheus-net.DotNetRuntime" Version="4.2.3" />
|
||||||
<PackageReference Include="sharpcompress" Version="0.30.0" />
|
<PackageReference Include="sharpcompress" Version="0.30.1" />
|
||||||
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="3.1.0" />
|
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="3.1.0" />
|
||||||
<PackageReference Include="DotNet.Glob" Version="3.1.3" />
|
<PackageReference Include="DotNet.Glob" Version="3.1.3" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
@@ -49,10 +49,18 @@
|
|||||||
<NoWarn>AD0001</NoWarn>
|
<NoWarn>AD0001</NoWarn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||||
|
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
<!-- Code Analyzers-->
|
<!-- Code Analyzers-->
|
||||||
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||||
|
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.3">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
|
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
|
||||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
|
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.406" PrivateAssets="All" />
|
||||||
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
|
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ using Jellyfin.Networking.Configuration;
|
|||||||
using MediaBrowser.Controller;
|
using MediaBrowser.Controller;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Controller.Plugins;
|
using MediaBrowser.Controller.Plugins;
|
||||||
using MediaBrowser.Model.Dlna;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Mono.Nat;
|
using Mono.Nat;
|
||||||
|
|
||||||
@@ -27,7 +26,6 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||||||
private readonly IServerApplicationHost _appHost;
|
private readonly IServerApplicationHost _appHost;
|
||||||
private readonly ILogger<ExternalPortForwarding> _logger;
|
private readonly ILogger<ExternalPortForwarding> _logger;
|
||||||
private readonly IServerConfigurationManager _config;
|
private readonly IServerConfigurationManager _config;
|
||||||
private readonly IDeviceDiscovery _deviceDiscovery;
|
|
||||||
|
|
||||||
private readonly ConcurrentDictionary<IPEndPoint, byte> _createdRules = new ConcurrentDictionary<IPEndPoint, byte>();
|
private readonly ConcurrentDictionary<IPEndPoint, byte> _createdRules = new ConcurrentDictionary<IPEndPoint, byte>();
|
||||||
|
|
||||||
@@ -42,17 +40,14 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||||||
/// <param name="logger">The logger.</param>
|
/// <param name="logger">The logger.</param>
|
||||||
/// <param name="appHost">The application host.</param>
|
/// <param name="appHost">The application host.</param>
|
||||||
/// <param name="config">The configuration manager.</param>
|
/// <param name="config">The configuration manager.</param>
|
||||||
/// <param name="deviceDiscovery">The device discovery.</param>
|
|
||||||
public ExternalPortForwarding(
|
public ExternalPortForwarding(
|
||||||
ILogger<ExternalPortForwarding> logger,
|
ILogger<ExternalPortForwarding> logger,
|
||||||
IServerApplicationHost appHost,
|
IServerApplicationHost appHost,
|
||||||
IServerConfigurationManager config,
|
IServerConfigurationManager config)
|
||||||
IDeviceDiscovery deviceDiscovery)
|
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_appHost = appHost;
|
_appHost = appHost;
|
||||||
_config = config;
|
_config = config;
|
||||||
_deviceDiscovery = deviceDiscovery;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetConfigIdentifier()
|
private string GetConfigIdentifier()
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_lastProgressMessageTimes.AddOrUpdate(item.Id, key => DateTime.UtcNow, (key, existing) => DateTime.UtcNow);
|
_lastProgressMessageTimes.AddOrUpdate(item.Id, _ => DateTime.UtcNow, (_, _) => DateTime.UtcNow);
|
||||||
|
|
||||||
var dict = new Dictionary<string, string>();
|
var dict = new Dictionary<string, string>();
|
||||||
dict["ItemId"] = item.Id.ToString("N", CultureInfo.InvariantCulture);
|
dict["ItemId"] = item.Id.ToString("N", CultureInfo.InvariantCulture);
|
||||||
@@ -144,7 +144,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||||||
{
|
{
|
||||||
OnProviderRefreshProgress(sender, new GenericEventArgs<Tuple<BaseItem, double>>(new Tuple<BaseItem, double>(e.Argument, 100)));
|
OnProviderRefreshProgress(sender, new GenericEventArgs<Tuple<BaseItem, double>>(new Tuple<BaseItem, double>(e.Argument, 100)));
|
||||||
|
|
||||||
_lastProgressMessageTimes.TryRemove(e.Argument.Id, out DateTime removed);
|
_lastProgressMessageTimes.TryRemove(e.Argument.Id, out _);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool EnableRefreshMessage(BaseItem item)
|
private static bool EnableRefreshMessage(BaseItem item)
|
||||||
@@ -326,7 +326,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||||||
{
|
{
|
||||||
var userIds = _sessionManager.Sessions
|
var userIds = _sessionManager.Sessions
|
||||||
.Select(i => i.UserId)
|
.Select(i => i.UserId)
|
||||||
.Where(i => !i.Equals(Guid.Empty))
|
.Where(i => !i.Equals(default))
|
||||||
.Distinct()
|
.Distinct()
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
@@ -423,7 +423,6 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var collectionFolders = _libraryManager.GetCollectionFolders(item, allUserRootChildren);
|
|
||||||
foreach (var folder in allUserRootChildren)
|
foreach (var folder in allUserRootChildren)
|
||||||
{
|
{
|
||||||
list.Add(folder.Id.ToString("N", CultureInfo.InvariantCulture));
|
list.Add(folder.Id.ToString("N", CultureInfo.InvariantCulture));
|
||||||
@@ -465,6 +464,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
Dispose(true);
|
Dispose(true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ using System.Net.Sockets;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Emby.Server.Implementations.Udp;
|
using Emby.Server.Implementations.Udp;
|
||||||
|
using Jellyfin.Networking.Configuration;
|
||||||
|
using MediaBrowser.Common.Configuration;
|
||||||
using MediaBrowser.Controller;
|
using MediaBrowser.Controller;
|
||||||
using MediaBrowser.Controller.Plugins;
|
using MediaBrowser.Controller.Plugins;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
@@ -26,6 +28,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||||||
private readonly ILogger<UdpServerEntryPoint> _logger;
|
private readonly ILogger<UdpServerEntryPoint> _logger;
|
||||||
private readonly IServerApplicationHost _appHost;
|
private readonly IServerApplicationHost _appHost;
|
||||||
private readonly IConfiguration _config;
|
private readonly IConfiguration _config;
|
||||||
|
private readonly IConfigurationManager _configurationManager;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The UDP server.
|
/// The UDP server.
|
||||||
@@ -40,14 +43,17 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||||||
/// <param name="logger">Instance of the <see cref="ILogger{UdpServerEntryPoint}"/> interface.</param>
|
/// <param name="logger">Instance of the <see cref="ILogger{UdpServerEntryPoint}"/> interface.</param>
|
||||||
/// <param name="appHost">Instance of the <see cref="IServerApplicationHost"/> interface.</param>
|
/// <param name="appHost">Instance of the <see cref="IServerApplicationHost"/> interface.</param>
|
||||||
/// <param name="configuration">Instance of the <see cref="IConfiguration"/> interface.</param>
|
/// <param name="configuration">Instance of the <see cref="IConfiguration"/> interface.</param>
|
||||||
|
/// <param name="configurationManager">Instance of the <see cref="IConfigurationManager"/> interface.</param>
|
||||||
public UdpServerEntryPoint(
|
public UdpServerEntryPoint(
|
||||||
ILogger<UdpServerEntryPoint> logger,
|
ILogger<UdpServerEntryPoint> logger,
|
||||||
IServerApplicationHost appHost,
|
IServerApplicationHost appHost,
|
||||||
IConfiguration configuration)
|
IConfiguration configuration,
|
||||||
|
IConfigurationManager configurationManager)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_appHost = appHost;
|
_appHost = appHost;
|
||||||
_config = configuration;
|
_config = configuration;
|
||||||
|
_configurationManager = configurationManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@@ -55,6 +61,11 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||||||
{
|
{
|
||||||
CheckDisposed();
|
CheckDisposed();
|
||||||
|
|
||||||
|
if (!_configurationManager.GetNetworkConfiguration().AutoDiscovery)
|
||||||
|
{
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_udpServer = new UdpServer(_logger, _appHost, _config, PortNumber);
|
_udpServer = new UdpServer(_logger, _appHost, _config, PortNumber);
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||||||
var changes = _changedItems.ToList();
|
var changes = _changedItems.ToList();
|
||||||
_changedItems.Clear();
|
_changedItems.Clear();
|
||||||
|
|
||||||
var task = SendNotifications(changes, CancellationToken.None);
|
SendNotifications(changes, CancellationToken.None).GetAwaiter().GetResult();
|
||||||
|
|
||||||
if (_updateTimer != null)
|
if (_updateTimer != null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Jellyfin.Data.Enums;
|
using Jellyfin.Data.Enums;
|
||||||
using MediaBrowser.Controller.Authentication;
|
|
||||||
using MediaBrowser.Controller.Net;
|
using MediaBrowser.Controller.Net;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
|
|
||||||
|
|||||||
@@ -47,7 +47,9 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
|||||||
{
|
{
|
||||||
var session = await GetSession(requestContext).ConfigureAwait(false);
|
var session = await GetSession(requestContext).ConfigureAwait(false);
|
||||||
|
|
||||||
return session == null || session.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(session.UserId);
|
return session.UserId.Equals(default)
|
||||||
|
? null
|
||||||
|
: _userManager.GetUserById(session.UserId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<User?> GetUser(object requestContext)
|
public Task<User?> GetUser(object requestContext)
|
||||||
|
|||||||
@@ -42,17 +42,14 @@ namespace Emby.Server.Implementations.HttpServer
|
|||||||
/// <param name="logger">The logger.</param>
|
/// <param name="logger">The logger.</param>
|
||||||
/// <param name="socket">The socket.</param>
|
/// <param name="socket">The socket.</param>
|
||||||
/// <param name="remoteEndPoint">The remote end point.</param>
|
/// <param name="remoteEndPoint">The remote end point.</param>
|
||||||
/// <param name="query">The query.</param>
|
|
||||||
public WebSocketConnection(
|
public WebSocketConnection(
|
||||||
ILogger<WebSocketConnection> logger,
|
ILogger<WebSocketConnection> logger,
|
||||||
WebSocket socket,
|
WebSocket socket,
|
||||||
IPAddress? remoteEndPoint,
|
IPAddress? remoteEndPoint)
|
||||||
IQueryCollection query)
|
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_socket = socket;
|
_socket = socket;
|
||||||
RemoteEndPoint = remoteEndPoint;
|
RemoteEndPoint = remoteEndPoint;
|
||||||
QueryString = query;
|
|
||||||
|
|
||||||
_jsonOptions = JsonDefaults.Options;
|
_jsonOptions = JsonDefaults.Options;
|
||||||
LastActivityDate = DateTime.Now;
|
LastActivityDate = DateTime.Now;
|
||||||
@@ -81,12 +78,6 @@ namespace Emby.Server.Implementations.HttpServer
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public DateTime LastKeepAliveDate { get; set; }
|
public DateTime LastKeepAliveDate { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the query string.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The query string.</value>
|
|
||||||
public IQueryCollection QueryString { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the state.
|
/// Gets the state.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -180,7 +171,7 @@ namespace Emby.Server.Implementations.HttpServer
|
|||||||
}
|
}
|
||||||
|
|
||||||
WebSocketMessage<object>? stub;
|
WebSocketMessage<object>? stub;
|
||||||
long bytesConsumed = 0;
|
long bytesConsumed;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
stub = DeserializeWebSocketMessage(buffer, out bytesConsumed);
|
stub = DeserializeWebSocketMessage(buffer, out bytesConsumed);
|
||||||
@@ -236,7 +227,8 @@ namespace Emby.Server.Implementations.HttpServer
|
|||||||
{
|
{
|
||||||
MessageId = Guid.NewGuid(),
|
MessageId = Guid.NewGuid(),
|
||||||
MessageType = SessionMessageType.KeepAlive
|
MessageType = SessionMessageType.KeepAlive
|
||||||
}, CancellationToken.None);
|
},
|
||||||
|
CancellationToken.None);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.WebSockets;
|
using System.Net.WebSockets;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using MediaBrowser.Common.Extensions;
|
||||||
using MediaBrowser.Controller.Net;
|
using MediaBrowser.Controller.Net;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
@@ -50,8 +51,7 @@ namespace Emby.Server.Implementations.HttpServer
|
|||||||
using var connection = new WebSocketConnection(
|
using var connection = new WebSocketConnection(
|
||||||
_loggerFactory.CreateLogger<WebSocketConnection>(),
|
_loggerFactory.CreateLogger<WebSocketConnection>(),
|
||||||
webSocket,
|
webSocket,
|
||||||
context.Connection.RemoteIpAddress,
|
context.GetNormalizedRemoteIp())
|
||||||
context.Request.Query)
|
|
||||||
{
|
{
|
||||||
OnReceive = ProcessWebSocketMessageReceived
|
OnReceive = ProcessWebSocketMessageReceived
|
||||||
};
|
};
|
||||||
@@ -59,7 +59,7 @@ namespace Emby.Server.Implementations.HttpServer
|
|||||||
var tasks = new Task[_webSocketListeners.Length];
|
var tasks = new Task[_webSocketListeners.Length];
|
||||||
for (var i = 0; i < _webSocketListeners.Length; ++i)
|
for (var i = 0; i < _webSocketListeners.Length; ++i)
|
||||||
{
|
{
|
||||||
tasks[i] = _webSocketListeners[i].ProcessWebSocketConnectedAsync(connection);
|
tasks[i] = _webSocketListeners[i].ProcessWebSocketConnectedAsync(connection, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
await Task.WhenAll(tasks).ConfigureAwait(false);
|
await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
#nullable disable
|
|
||||||
|
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
@@ -14,7 +12,7 @@ using Microsoft.Extensions.Logging;
|
|||||||
|
|
||||||
namespace Emby.Server.Implementations.IO
|
namespace Emby.Server.Implementations.IO
|
||||||
{
|
{
|
||||||
public class FileRefresher : IDisposable
|
public sealed class FileRefresher : IDisposable
|
||||||
{
|
{
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
@@ -22,7 +20,7 @@ namespace Emby.Server.Implementations.IO
|
|||||||
|
|
||||||
private readonly List<string> _affectedPaths = new List<string>();
|
private readonly List<string> _affectedPaths = new List<string>();
|
||||||
private readonly object _timerLock = new object();
|
private readonly object _timerLock = new object();
|
||||||
private Timer _timer;
|
private Timer? _timer;
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
|
|
||||||
public FileRefresher(string path, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, ILogger logger)
|
public FileRefresher(string path, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, ILogger logger)
|
||||||
@@ -36,7 +34,7 @@ namespace Emby.Server.Implementations.IO
|
|||||||
AddPath(path);
|
AddPath(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
public event EventHandler<EventArgs> Completed;
|
public event EventHandler<EventArgs>? Completed;
|
||||||
|
|
||||||
public string Path { get; private set; }
|
public string Path { get; private set; }
|
||||||
|
|
||||||
@@ -111,7 +109,7 @@ namespace Emby.Server.Implementations.IO
|
|||||||
RestartTimer();
|
RestartTimer();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnTimerCallback(object state)
|
private void OnTimerCallback(object? state)
|
||||||
{
|
{
|
||||||
List<string> paths;
|
List<string> paths;
|
||||||
|
|
||||||
@@ -127,7 +125,7 @@ namespace Emby.Server.Implementations.IO
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
ProcessPathChanges(paths.ToList());
|
ProcessPathChanges(paths);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -137,12 +135,12 @@ namespace Emby.Server.Implementations.IO
|
|||||||
|
|
||||||
private void ProcessPathChanges(List<string> paths)
|
private void ProcessPathChanges(List<string> paths)
|
||||||
{
|
{
|
||||||
var itemsToRefresh = paths
|
IEnumerable<BaseItem> itemsToRefresh = paths
|
||||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||||
.Select(GetAffectedBaseItem)
|
.Select(GetAffectedBaseItem)
|
||||||
.Where(item => item != null)
|
.Where(item => item != null)
|
||||||
.GroupBy(x => x.Id)
|
.GroupBy(x => x!.Id) // Removed null values in the previous .Where()
|
||||||
.Select(x => x.First());
|
.Select(x => x.First())!;
|
||||||
|
|
||||||
foreach (var item in itemsToRefresh)
|
foreach (var item in itemsToRefresh)
|
||||||
{
|
{
|
||||||
@@ -176,15 +174,15 @@ namespace Emby.Server.Implementations.IO
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="path">The path.</param>
|
/// <param name="path">The path.</param>
|
||||||
/// <returns>BaseItem.</returns>
|
/// <returns>BaseItem.</returns>
|
||||||
private BaseItem GetAffectedBaseItem(string path)
|
private BaseItem? GetAffectedBaseItem(string path)
|
||||||
{
|
{
|
||||||
BaseItem item = null;
|
BaseItem? item = null;
|
||||||
|
|
||||||
while (item == null && !string.IsNullOrEmpty(path))
|
while (item == null && !string.IsNullOrEmpty(path))
|
||||||
{
|
{
|
||||||
item = _libraryManager.FindByPath(path, null);
|
item = _libraryManager.FindByPath(path, null);
|
||||||
|
|
||||||
path = System.IO.Path.GetDirectoryName(path);
|
path = System.IO.Path.GetDirectoryName(path) ?? string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item != null)
|
if (item != null)
|
||||||
@@ -219,8 +217,13 @@ namespace Emby.Server.Implementations.IO
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_disposed = true;
|
if (_disposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
DisposeTimer();
|
DisposeTimer();
|
||||||
|
_disposed = true;
|
||||||
GC.SuppressFinalize(this);
|
GC.SuppressFinalize(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ namespace Emby.Server.Implementations.IO
|
|||||||
// But if we make this delay too high, we risk missing legitimate changes, such as user adding a new file, or hand-editing metadata
|
// But if we make this delay too high, we risk missing legitimate changes, such as user adding a new file, or hand-editing metadata
|
||||||
await Task.Delay(45000).ConfigureAwait(false);
|
await Task.Delay(45000).ConfigureAwait(false);
|
||||||
|
|
||||||
_tempIgnoredPaths.TryRemove(path, out var val);
|
_tempIgnoredPaths.TryRemove(path, out _);
|
||||||
|
|
||||||
if (refreshPath)
|
if (refreshPath)
|
||||||
{
|
{
|
||||||
@@ -449,12 +449,12 @@ namespace Emby.Server.Implementations.IO
|
|||||||
}
|
}
|
||||||
|
|
||||||
var newRefresher = new FileRefresher(path, _configurationManager, _libraryManager, _logger);
|
var newRefresher = new FileRefresher(path, _configurationManager, _libraryManager, _logger);
|
||||||
newRefresher.Completed += NewRefresher_Completed;
|
newRefresher.Completed += OnNewRefresherCompleted;
|
||||||
_activeRefreshers.Add(newRefresher);
|
_activeRefreshers.Add(newRefresher);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void NewRefresher_Completed(object sender, EventArgs e)
|
private void OnNewRefresherCompleted(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
var refresher = (FileRefresher)sender;
|
var refresher = (FileRefresher)sender;
|
||||||
DisposeRefresher(refresher);
|
DisposeRefresher(refresher);
|
||||||
@@ -481,6 +481,7 @@ namespace Emby.Server.Implementations.IO
|
|||||||
{
|
{
|
||||||
lock (_activeRefreshers)
|
lock (_activeRefreshers)
|
||||||
{
|
{
|
||||||
|
refresher.Completed -= OnNewRefresherCompleted;
|
||||||
refresher.Dispose();
|
refresher.Dispose();
|
||||||
_activeRefreshers.Remove(refresher);
|
_activeRefreshers.Remove(refresher);
|
||||||
}
|
}
|
||||||
@@ -492,6 +493,7 @@ namespace Emby.Server.Implementations.IO
|
|||||||
{
|
{
|
||||||
foreach (var refresher in _activeRefreshers.ToList())
|
foreach (var refresher in _activeRefreshers.ToList())
|
||||||
{
|
{
|
||||||
|
refresher.Completed -= OnNewRefresherCompleted;
|
||||||
refresher.Dispose();
|
refresher.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -544,16 +544,6 @@ namespace Emby.Server.Implementations.IO
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public virtual bool AreEqual(string path1, string path2)
|
public virtual bool AreEqual(string path1, string path2)
|
||||||
{
|
{
|
||||||
if (path1 == null && path2 == null)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (path1 == null || path2 == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return string.Equals(
|
return string.Equals(
|
||||||
NormalizePath(path1),
|
NormalizePath(path1),
|
||||||
NormalizePath(path2),
|
NormalizePath(path2),
|
||||||
@@ -591,7 +581,7 @@ namespace Emby.Server.Implementations.IO
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public virtual List<FileSystemMetadata> GetDrives()
|
public virtual IEnumerable<FileSystemMetadata> GetDrives()
|
||||||
{
|
{
|
||||||
// check for ready state to avoid waiting for drives to timeout
|
// check for ready state to avoid waiting for drives to timeout
|
||||||
// some drives on linux have no actual size or are used for other purposes
|
// some drives on linux have no actual size or are used for other purposes
|
||||||
@@ -605,7 +595,7 @@ namespace Emby.Server.Implementations.IO
|
|||||||
Name = d.Name,
|
Name = d.Name,
|
||||||
FullName = d.RootDirectory.FullName,
|
FullName = d.RootDirectory.FullName,
|
||||||
IsDirectory = true
|
IsDirectory = true
|
||||||
}).ToList();
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@@ -714,6 +704,18 @@ namespace Emby.Server.Implementations.IO
|
|||||||
return Directory.EnumerateFileSystemEntries(path, "*", GetEnumerationOptions(recursive));
|
return Directory.EnumerateFileSystemEntries(path, "*", GetEnumerationOptions(recursive));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public virtual bool DirectoryExists(string path)
|
||||||
|
{
|
||||||
|
return Directory.Exists(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public virtual bool FileExists(string path)
|
||||||
|
{
|
||||||
|
return File.Exists(path);
|
||||||
|
}
|
||||||
|
|
||||||
private EnumerationOptions GetEnumerationOptions(bool recursive)
|
private EnumerationOptions GetEnumerationOptions(bool recursive)
|
||||||
{
|
{
|
||||||
return new EnumerationOptions
|
return new EnumerationOptions
|
||||||
|
|||||||
@@ -17,11 +17,11 @@ namespace Emby.Server.Implementations.IO
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
int read;
|
int read;
|
||||||
while ((read = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0)
|
while ((read = await source.ReadAsync(buffer, cancellationToken).ConfigureAwait(false)) != 0)
|
||||||
{
|
{
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
await destination.WriteAsync(buffer, 0, read, cancellationToken).ConfigureAwait(false);
|
await destination.WriteAsync(buffer.AsMemory(0, read), cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
if (onStarted != null)
|
if (onStarted != null)
|
||||||
{
|
{
|
||||||
@@ -44,11 +44,11 @@ namespace Emby.Server.Implementations.IO
|
|||||||
if (emptyReadLimit <= 0)
|
if (emptyReadLimit <= 0)
|
||||||
{
|
{
|
||||||
int read;
|
int read;
|
||||||
while ((read = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0)
|
while ((read = await source.ReadAsync(buffer, cancellationToken).ConfigureAwait(false)) != 0)
|
||||||
{
|
{
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
await destination.WriteAsync(buffer, 0, read, cancellationToken).ConfigureAwait(false);
|
await destination.WriteAsync(buffer.AsMemory(0, read), cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
@@ -60,7 +60,7 @@ namespace Emby.Server.Implementations.IO
|
|||||||
{
|
{
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
var bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
|
var bytesRead = await source.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
if (bytesRead == 0)
|
if (bytesRead == 0)
|
||||||
{
|
{
|
||||||
@@ -71,7 +71,7 @@ namespace Emby.Server.Implementations.IO
|
|||||||
{
|
{
|
||||||
eofCount = 0;
|
eofCount = 0;
|
||||||
|
|
||||||
await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false);
|
await destination.WriteAsync(buffer.AsMemory(0, bytesRead), cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -88,13 +88,13 @@ namespace Emby.Server.Implementations.IO
|
|||||||
{
|
{
|
||||||
int bytesRead;
|
int bytesRead;
|
||||||
|
|
||||||
while ((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0)
|
while ((bytesRead = await source.ReadAsync(buffer, cancellationToken).ConfigureAwait(false)) != 0)
|
||||||
{
|
{
|
||||||
var bytesToWrite = Math.Min(bytesRead, copyLength);
|
var bytesToWrite = Math.Min(bytesRead, copyLength);
|
||||||
|
|
||||||
if (bytesToWrite > 0)
|
if (bytesToWrite > 0)
|
||||||
{
|
{
|
||||||
await destination.WriteAsync(buffer, 0, Convert.ToInt32(bytesToWrite), cancellationToken).ConfigureAwait(false);
|
await destination.WriteAsync(buffer.AsMemory(0, Convert.ToInt32(bytesToWrite)), cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
copyLength -= bytesToWrite;
|
copyLength -= bytesToWrite;
|
||||||
@@ -137,9 +137,9 @@ namespace Emby.Server.Implementations.IO
|
|||||||
int bytesRead;
|
int bytesRead;
|
||||||
int totalBytesRead = 0;
|
int totalBytesRead = 0;
|
||||||
|
|
||||||
while ((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0)
|
while ((bytesRead = await source.ReadAsync(buffer, cancellationToken).ConfigureAwait(false)) != 0)
|
||||||
{
|
{
|
||||||
await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false);
|
await destination.WriteAsync(buffer.AsMemory(0, bytesRead), cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
totalBytesRead += bytesRead;
|
totalBytesRead += bytesRead;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ namespace Emby.Server.Implementations.Images
|
|||||||
|
|
||||||
protected virtual IEnumerable<string> GetStripCollageImagePaths(BaseItem primaryItem, IEnumerable<BaseItem> items)
|
protected virtual IEnumerable<string> GetStripCollageImagePaths(BaseItem primaryItem, IEnumerable<BaseItem> items)
|
||||||
{
|
{
|
||||||
var useBackdrop = primaryItem is CollectionFolder;
|
var useBackdrop = primaryItem is CollectionFolder || primaryItem is UserView;
|
||||||
return items
|
return items
|
||||||
.Select(i =>
|
.Select(i =>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ namespace Emby.Server.Implementations.Images
|
|||||||
{
|
{
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
|
|
||||||
public BaseFolderImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, ILibraryManager libraryManager)
|
protected BaseFolderImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, ILibraryManager libraryManager)
|
||||||
: base(fileSystem, providerManager, applicationPaths, imageProcessor)
|
: base(fileSystem, providerManager, applicationPaths, imageProcessor)
|
||||||
{
|
{
|
||||||
_libraryManager = libraryManager;
|
_libraryManager = libraryManager;
|
||||||
|
|||||||
@@ -28,35 +28,39 @@ namespace Emby.Server.Implementations.Images
|
|||||||
var view = (CollectionFolder)item;
|
var view = (CollectionFolder)item;
|
||||||
var viewType = view.CollectionType;
|
var viewType = view.CollectionType;
|
||||||
|
|
||||||
string[] includeItemTypes;
|
BaseItemKind[] includeItemTypes;
|
||||||
|
|
||||||
if (string.Equals(viewType, CollectionType.Movies, StringComparison.Ordinal))
|
if (string.Equals(viewType, CollectionType.Movies, StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
includeItemTypes = new string[] { "Movie" };
|
includeItemTypes = new[] { BaseItemKind.Movie };
|
||||||
}
|
}
|
||||||
else if (string.Equals(viewType, CollectionType.TvShows, StringComparison.Ordinal))
|
else if (string.Equals(viewType, CollectionType.TvShows, StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
includeItemTypes = new string[] { "Series" };
|
includeItemTypes = new[] { BaseItemKind.Series };
|
||||||
}
|
}
|
||||||
else if (string.Equals(viewType, CollectionType.Music, StringComparison.Ordinal))
|
else if (string.Equals(viewType, CollectionType.Music, StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
includeItemTypes = new string[] { "MusicAlbum" };
|
includeItemTypes = new[] { BaseItemKind.MusicAlbum };
|
||||||
|
}
|
||||||
|
else if (string.Equals(viewType, CollectionType.MusicVideos, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
includeItemTypes = new[] { BaseItemKind.MusicVideo };
|
||||||
}
|
}
|
||||||
else if (string.Equals(viewType, CollectionType.Books, StringComparison.Ordinal))
|
else if (string.Equals(viewType, CollectionType.Books, StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
includeItemTypes = new string[] { "Book", "AudioBook" };
|
includeItemTypes = new[] { BaseItemKind.Book, BaseItemKind.AudioBook };
|
||||||
}
|
}
|
||||||
else if (string.Equals(viewType, CollectionType.BoxSets, StringComparison.Ordinal))
|
else if (string.Equals(viewType, CollectionType.BoxSets, StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
includeItemTypes = new string[] { "BoxSet" };
|
includeItemTypes = new[] { BaseItemKind.BoxSet };
|
||||||
}
|
}
|
||||||
else if (string.Equals(viewType, CollectionType.HomeVideos, StringComparison.Ordinal) || string.Equals(viewType, CollectionType.Photos, StringComparison.Ordinal))
|
else if (string.Equals(viewType, CollectionType.HomeVideos, StringComparison.Ordinal) || string.Equals(viewType, CollectionType.Photos, StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
includeItemTypes = new string[] { "Video", "Photo" };
|
includeItemTypes = new[] { BaseItemKind.Video, BaseItemKind.Photo };
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
includeItemTypes = new string[] { "Video", "Audio", "Photo", "Movie", "Series" };
|
includeItemTypes = new[] { BaseItemKind.Video, BaseItemKind.Audio, BaseItemKind.Photo, BaseItemKind.Movie, BaseItemKind.Series };
|
||||||
}
|
}
|
||||||
|
|
||||||
var recursive = !string.Equals(CollectionType.Playlists, viewType, StringComparison.OrdinalIgnoreCase);
|
var recursive = !string.Equals(CollectionType.Playlists, viewType, StringComparison.OrdinalIgnoreCase);
|
||||||
@@ -68,9 +72,9 @@ namespace Emby.Server.Implementations.Images
|
|||||||
DtoOptions = new DtoOptions(false),
|
DtoOptions = new DtoOptions(false),
|
||||||
ImageTypes = new ImageType[] { ImageType.Primary },
|
ImageTypes = new ImageType[] { ImageType.Primary },
|
||||||
Limit = 8,
|
Limit = 8,
|
||||||
OrderBy = new ValueTuple<string, SortOrder>[]
|
OrderBy = new[]
|
||||||
{
|
{
|
||||||
new ValueTuple<string, SortOrder>(ItemSortBy.Random, SortOrder.Ascending)
|
(ItemSortBy.Random, SortOrder.Ascending)
|
||||||
},
|
},
|
||||||
IncludeItemTypes = includeItemTypes
|
IncludeItemTypes = includeItemTypes
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Jellyfin.Data.Enums;
|
||||||
|
using Jellyfin.Extensions;
|
||||||
using MediaBrowser.Common.Configuration;
|
using MediaBrowser.Common.Configuration;
|
||||||
using MediaBrowser.Controller.Drawing;
|
using MediaBrowser.Controller.Drawing;
|
||||||
using MediaBrowser.Controller.Dto;
|
using MediaBrowser.Controller.Dto;
|
||||||
@@ -34,14 +36,14 @@ namespace Emby.Server.Implementations.Images
|
|||||||
var view = (UserView)item;
|
var view = (UserView)item;
|
||||||
|
|
||||||
var isUsingCollectionStrip = IsUsingCollectionStrip(view);
|
var isUsingCollectionStrip = IsUsingCollectionStrip(view);
|
||||||
var recursive = isUsingCollectionStrip && !new[] { CollectionType.BoxSets, CollectionType.Playlists }.Contains(view.ViewType ?? string.Empty, StringComparer.OrdinalIgnoreCase);
|
var recursive = isUsingCollectionStrip && !new[] { CollectionType.BoxSets, CollectionType.Playlists }.Contains(view.ViewType ?? string.Empty, StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
var result = view.GetItemList(new InternalItemsQuery
|
var result = view.GetItemList(new InternalItemsQuery
|
||||||
{
|
{
|
||||||
User = view.UserId.HasValue ? _userManager.GetUserById(view.UserId.Value) : null,
|
User = view.UserId.HasValue ? _userManager.GetUserById(view.UserId.Value) : null,
|
||||||
CollapseBoxSetItems = false,
|
CollapseBoxSetItems = false,
|
||||||
Recursive = recursive,
|
Recursive = recursive,
|
||||||
ExcludeItemTypes = new[] { "UserView", "CollectionFolder", "Person" },
|
ExcludeItemTypes = new[] { BaseItemKind.UserView, BaseItemKind.CollectionFolder, BaseItemKind.Person },
|
||||||
DtoOptions = new DtoOptions(false)
|
DtoOptions = new DtoOptions(false)
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -82,16 +84,20 @@ namespace Emby.Server.Implementations.Images
|
|||||||
}).GroupBy(x => x.Id)
|
}).GroupBy(x => x.Id)
|
||||||
.Select(x => x.First());
|
.Select(x => x.First());
|
||||||
|
|
||||||
|
List<BaseItem> returnItems;
|
||||||
if (isUsingCollectionStrip)
|
if (isUsingCollectionStrip)
|
||||||
{
|
{
|
||||||
return items
|
returnItems = items
|
||||||
.Where(i => i.HasImage(ImageType.Primary) || i.HasImage(ImageType.Thumb))
|
.Where(i => i.HasImage(ImageType.Primary) || i.HasImage(ImageType.Thumb))
|
||||||
.ToList();
|
.ToList();
|
||||||
|
returnItems.Shuffle();
|
||||||
|
return returnItems;
|
||||||
}
|
}
|
||||||
|
returnItems = items
|
||||||
return items
|
|
||||||
.Where(i => i.HasImage(ImageType.Primary))
|
.Where(i => i.HasImage(ImageType.Primary))
|
||||||
.ToList();
|
.ToList();
|
||||||
|
returnItems.Shuffle();
|
||||||
|
return returnItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool Supports(BaseItem item)
|
protected override bool Supports(BaseItem item)
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ using MediaBrowser.Common.Configuration;
|
|||||||
using MediaBrowser.Controller.Drawing;
|
using MediaBrowser.Controller.Drawing;
|
||||||
using MediaBrowser.Controller.Dto;
|
using MediaBrowser.Controller.Dto;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Entities.Movies;
|
|
||||||
using MediaBrowser.Controller.Entities.TV;
|
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.Providers;
|
using MediaBrowser.Controller.Providers;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
@@ -43,7 +41,7 @@ namespace Emby.Server.Implementations.Images
|
|||||||
return _libraryManager.GetItemList(new InternalItemsQuery
|
return _libraryManager.GetItemList(new InternalItemsQuery
|
||||||
{
|
{
|
||||||
Genres = new[] { item.Name },
|
Genres = new[] { item.Name },
|
||||||
IncludeItemTypes = new[] { nameof(Series), nameof(Movie) },
|
IncludeItemTypes = new[] { BaseItemKind.Series, BaseItemKind.Movie },
|
||||||
OrderBy = new[] { (ItemSortBy.Random, SortOrder.Ascending) },
|
OrderBy = new[] { (ItemSortBy.Random, SortOrder.Ascending) },
|
||||||
Limit = 4,
|
Limit = 4,
|
||||||
Recursive = true,
|
Recursive = true,
|
||||||
|
|||||||
@@ -44,9 +44,9 @@ namespace Emby.Server.Implementations.Images
|
|||||||
Genres = new[] { item.Name },
|
Genres = new[] { item.Name },
|
||||||
IncludeItemTypes = new[]
|
IncludeItemTypes = new[]
|
||||||
{
|
{
|
||||||
nameof(MusicAlbum),
|
BaseItemKind.MusicAlbum,
|
||||||
nameof(MusicVideo),
|
BaseItemKind.MusicVideo,
|
||||||
nameof(Audio)
|
BaseItemKind.Audio
|
||||||
},
|
},
|
||||||
OrderBy = new[] { (ItemSortBy.Random, SortOrder.Ascending) },
|
OrderBy = new[] { (ItemSortBy.Random, SortOrder.Ascending) },
|
||||||
Limit = 4,
|
Limit = 4,
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using Emby.Naming.Audio;
|
||||||
|
using Emby.Naming.Common;
|
||||||
using MediaBrowser.Controller;
|
using MediaBrowser.Controller;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Library;
|
|
||||||
using MediaBrowser.Controller.Resolvers;
|
using MediaBrowser.Controller.Resolvers;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
|
|
||||||
@@ -13,17 +14,17 @@ namespace Emby.Server.Implementations.Library
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class CoreResolutionIgnoreRule : IResolverIgnoreRule
|
public class CoreResolutionIgnoreRule : IResolverIgnoreRule
|
||||||
{
|
{
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly NamingOptions _namingOptions;
|
||||||
private readonly IServerApplicationPaths _serverApplicationPaths;
|
private readonly IServerApplicationPaths _serverApplicationPaths;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="CoreResolutionIgnoreRule"/> class.
|
/// Initializes a new instance of the <see cref="CoreResolutionIgnoreRule"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="libraryManager">The library manager.</param>
|
/// <param name="namingOptions">The naming options.</param>
|
||||||
/// <param name="serverApplicationPaths">The server application paths.</param>
|
/// <param name="serverApplicationPaths">The server application paths.</param>
|
||||||
public CoreResolutionIgnoreRule(ILibraryManager libraryManager, IServerApplicationPaths serverApplicationPaths)
|
public CoreResolutionIgnoreRule(NamingOptions namingOptions, IServerApplicationPaths serverApplicationPaths)
|
||||||
{
|
{
|
||||||
_libraryManager = libraryManager;
|
_namingOptions = namingOptions;
|
||||||
_serverApplicationPaths = serverApplicationPaths;
|
_serverApplicationPaths = serverApplicationPaths;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,20 +54,10 @@ namespace Emby.Server.Implementations.Library
|
|||||||
{
|
{
|
||||||
if (parent != null)
|
if (parent != null)
|
||||||
{
|
{
|
||||||
// Ignore trailer folders but allow it at the collection level
|
// Ignore extras folders but allow it at the collection level
|
||||||
if (string.Equals(filename, BaseItem.TrailersFolderName, StringComparison.OrdinalIgnoreCase)
|
if (_namingOptions.AllExtrasTypesFolderNames.ContainsKey(filename)
|
||||||
&& !(parent is AggregateFolder)
|
&& parent is not AggregateFolder
|
||||||
&& !(parent is UserRootFolder))
|
&& parent is not UserRootFolder)
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.Equals(filename, BaseItem.ThemeVideosFolderName, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.Equals(filename, BaseItem.ThemeSongsFolderName, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -78,7 +69,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
{
|
{
|
||||||
// Don't resolve these into audio files
|
// Don't resolve these into audio files
|
||||||
if (Path.GetFileNameWithoutExtension(filename.AsSpan()).Equals(BaseItem.ThemeSongFileName, StringComparison.Ordinal)
|
if (Path.GetFileNameWithoutExtension(filename.AsSpan()).Equals(BaseItem.ThemeSongFileName, StringComparison.Ordinal)
|
||||||
&& _libraryManager.IsAudioFile(filename))
|
&& AudioFileParser.IsAudioFile(filename, _namingOptions))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,14 +11,12 @@ using System.Net;
|
|||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Emby.Naming.Audio;
|
|
||||||
using Emby.Naming.Common;
|
using Emby.Naming.Common;
|
||||||
using Emby.Naming.TV;
|
using Emby.Naming.TV;
|
||||||
using Emby.Naming.Video;
|
|
||||||
using Emby.Server.Implementations.Library.Resolvers;
|
using Emby.Server.Implementations.Library.Resolvers;
|
||||||
using Emby.Server.Implementations.Library.Validators;
|
using Emby.Server.Implementations.Library.Validators;
|
||||||
using Emby.Server.Implementations.Playlists;
|
using Emby.Server.Implementations.Playlists;
|
||||||
using Emby.Server.Implementations.ScheduledTasks;
|
using Emby.Server.Implementations.ScheduledTasks.Tasks;
|
||||||
using Jellyfin.Data.Entities;
|
using Jellyfin.Data.Entities;
|
||||||
using Jellyfin.Data.Enums;
|
using Jellyfin.Data.Enums;
|
||||||
using Jellyfin.Extensions;
|
using Jellyfin.Extensions;
|
||||||
@@ -47,8 +45,8 @@ using MediaBrowser.Model.IO;
|
|||||||
using MediaBrowser.Model.Library;
|
using MediaBrowser.Model.Library;
|
||||||
using MediaBrowser.Model.Querying;
|
using MediaBrowser.Model.Querying;
|
||||||
using MediaBrowser.Model.Tasks;
|
using MediaBrowser.Model.Tasks;
|
||||||
using MediaBrowser.Providers.MediaInfo;
|
|
||||||
using Microsoft.Extensions.Caching.Memory;
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Episode = MediaBrowser.Controller.Entities.TV.Episode;
|
using Episode = MediaBrowser.Controller.Entities.TV.Episode;
|
||||||
using EpisodeInfo = Emby.Naming.TV.EpisodeInfo;
|
using EpisodeInfo = Emby.Naming.TV.EpisodeInfo;
|
||||||
@@ -79,6 +77,8 @@ namespace Emby.Server.Implementations.Library
|
|||||||
private readonly IFileSystem _fileSystem;
|
private readonly IFileSystem _fileSystem;
|
||||||
private readonly IItemRepository _itemRepository;
|
private readonly IItemRepository _itemRepository;
|
||||||
private readonly IImageProcessor _imageProcessor;
|
private readonly IImageProcessor _imageProcessor;
|
||||||
|
private readonly NamingOptions _namingOptions;
|
||||||
|
private readonly ExtraResolver _extraResolver;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The _root folder sync lock.
|
/// The _root folder sync lock.
|
||||||
@@ -88,9 +88,6 @@ namespace Emby.Server.Implementations.Library
|
|||||||
|
|
||||||
private readonly TimeSpan _viewRefreshInterval = TimeSpan.FromHours(24);
|
private readonly TimeSpan _viewRefreshInterval = TimeSpan.FromHours(24);
|
||||||
|
|
||||||
private NamingOptions _namingOptions;
|
|
||||||
private string[] _videoFileExtensions;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The _root folder.
|
/// The _root folder.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -103,7 +100,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
/// Initializes a new instance of the <see cref="LibraryManager" /> class.
|
/// Initializes a new instance of the <see cref="LibraryManager" /> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="appHost">The application host.</param>
|
/// <param name="appHost">The application host.</param>
|
||||||
/// <param name="logger">The logger.</param>
|
/// <param name="loggerFactory">The logger factory.</param>
|
||||||
/// <param name="taskManager">The task manager.</param>
|
/// <param name="taskManager">The task manager.</param>
|
||||||
/// <param name="userManager">The user manager.</param>
|
/// <param name="userManager">The user manager.</param>
|
||||||
/// <param name="configurationManager">The configuration manager.</param>
|
/// <param name="configurationManager">The configuration manager.</param>
|
||||||
@@ -116,9 +113,10 @@ namespace Emby.Server.Implementations.Library
|
|||||||
/// <param name="itemRepository">The item repository.</param>
|
/// <param name="itemRepository">The item repository.</param>
|
||||||
/// <param name="imageProcessor">The image processor.</param>
|
/// <param name="imageProcessor">The image processor.</param>
|
||||||
/// <param name="memoryCache">The memory cache.</param>
|
/// <param name="memoryCache">The memory cache.</param>
|
||||||
|
/// <param name="namingOptions">The naming options.</param>
|
||||||
public LibraryManager(
|
public LibraryManager(
|
||||||
IServerApplicationHost appHost,
|
IServerApplicationHost appHost,
|
||||||
ILogger<LibraryManager> logger,
|
ILoggerFactory loggerFactory,
|
||||||
ITaskManager taskManager,
|
ITaskManager taskManager,
|
||||||
IUserManager userManager,
|
IUserManager userManager,
|
||||||
IServerConfigurationManager configurationManager,
|
IServerConfigurationManager configurationManager,
|
||||||
@@ -130,10 +128,11 @@ namespace Emby.Server.Implementations.Library
|
|||||||
IMediaEncoder mediaEncoder,
|
IMediaEncoder mediaEncoder,
|
||||||
IItemRepository itemRepository,
|
IItemRepository itemRepository,
|
||||||
IImageProcessor imageProcessor,
|
IImageProcessor imageProcessor,
|
||||||
IMemoryCache memoryCache)
|
IMemoryCache memoryCache,
|
||||||
|
NamingOptions namingOptions)
|
||||||
{
|
{
|
||||||
_appHost = appHost;
|
_appHost = appHost;
|
||||||
_logger = logger;
|
_logger = loggerFactory.CreateLogger<LibraryManager>();
|
||||||
_taskManager = taskManager;
|
_taskManager = taskManager;
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
_configurationManager = configurationManager;
|
_configurationManager = configurationManager;
|
||||||
@@ -146,6 +145,9 @@ namespace Emby.Server.Implementations.Library
|
|||||||
_itemRepository = itemRepository;
|
_itemRepository = itemRepository;
|
||||||
_imageProcessor = imageProcessor;
|
_imageProcessor = imageProcessor;
|
||||||
_memoryCache = memoryCache;
|
_memoryCache = memoryCache;
|
||||||
|
_namingOptions = namingOptions;
|
||||||
|
|
||||||
|
_extraResolver = new ExtraResolver(loggerFactory.CreateLogger<ExtraResolver>(), namingOptions);
|
||||||
|
|
||||||
_configurationManager.ConfigurationUpdated += ConfigurationUpdated;
|
_configurationManager.ConfigurationUpdated += ConfigurationUpdated;
|
||||||
|
|
||||||
@@ -532,8 +534,8 @@ namespace Emby.Server.Implementations.Library
|
|||||||
return key.GetMD5();
|
return key.GetMD5();
|
||||||
}
|
}
|
||||||
|
|
||||||
public BaseItem ResolvePath(FileSystemMetadata fileInfo, Folder parent = null)
|
public BaseItem ResolvePath(FileSystemMetadata fileInfo, Folder parent = null, IDirectoryService directoryService = null)
|
||||||
=> ResolvePath(fileInfo, new DirectoryService(_fileSystem), null, parent);
|
=> ResolvePath(fileInfo, directoryService ?? new DirectoryService(_fileSystem), null, parent);
|
||||||
|
|
||||||
private BaseItem ResolvePath(
|
private BaseItem ResolvePath(
|
||||||
FileSystemMetadata fileInfo,
|
FileSystemMetadata fileInfo,
|
||||||
@@ -653,7 +655,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
return !args.ContainsFileSystemEntryByName(".ignore");
|
return !args.ContainsFileSystemEntryByName(".ignore");
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<BaseItem> ResolvePaths(IEnumerable<FileSystemMetadata> files, IDirectoryService directoryService, Folder parent, LibraryOptions libraryOptions, string collectionType)
|
public IEnumerable<BaseItem> ResolvePaths(IEnumerable<FileSystemMetadata> files, IDirectoryService directoryService, Folder parent, LibraryOptions libraryOptions, string collectionType = null)
|
||||||
{
|
{
|
||||||
return ResolvePaths(files, directoryService, parent, libraryOptions, collectionType, EntityResolvers);
|
return ResolvePaths(files, directoryService, parent, libraryOptions, collectionType, EntityResolvers);
|
||||||
}
|
}
|
||||||
@@ -676,11 +678,9 @@ namespace Emby.Server.Implementations.Library
|
|||||||
{
|
{
|
||||||
var result = resolver.ResolveMultiple(parent, fileList, collectionType, directoryService);
|
var result = resolver.ResolveMultiple(parent, fileList, collectionType, directoryService);
|
||||||
|
|
||||||
if (result != null && result.Items.Count > 0)
|
if (result?.Items.Count > 0)
|
||||||
{
|
{
|
||||||
var items = new List<BaseItem>();
|
var items = result.Items;
|
||||||
items.AddRange(result.Items);
|
|
||||||
|
|
||||||
foreach (var item in items)
|
foreach (var item in items)
|
||||||
{
|
{
|
||||||
ResolverHelper.SetInitialItemValues(item, parent, this, directoryService);
|
ResolverHelper.SetInitialItemValues(item, parent, this, directoryService);
|
||||||
@@ -756,7 +756,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
Path = path
|
Path = path
|
||||||
};
|
};
|
||||||
|
|
||||||
if (folder.Id.Equals(Guid.Empty))
|
if (folder.Id.Equals(default))
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(folder.Path))
|
if (string.IsNullOrEmpty(folder.Path))
|
||||||
{
|
{
|
||||||
@@ -775,7 +775,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
folder = dbItem;
|
folder = dbItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (folder.ParentId != rootFolder.Id)
|
if (!folder.ParentId.Equals(rootFolder.Id))
|
||||||
{
|
{
|
||||||
folder.ParentId = rootFolder.Id;
|
folder.ParentId = rootFolder.Id;
|
||||||
folder.UpdateToRepositoryAsync(ItemUpdateType.MetadataImport, CancellationToken.None).GetAwaiter().GetResult();
|
folder.UpdateToRepositoryAsync(ItemUpdateType.MetadataImport, CancellationToken.None).GetAwaiter().GetResult();
|
||||||
@@ -964,7 +964,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
{
|
{
|
||||||
var existing = GetItemList(new InternalItemsQuery
|
var existing = GetItemList(new InternalItemsQuery
|
||||||
{
|
{
|
||||||
IncludeItemTypes = new[] { nameof(MusicArtist) },
|
IncludeItemTypes = new[] { BaseItemKind.MusicArtist },
|
||||||
Name = name,
|
Name = name,
|
||||||
DtoOptions = options
|
DtoOptions = options
|
||||||
}).Cast<MusicArtist>()
|
}).Cast<MusicArtist>()
|
||||||
@@ -1005,14 +1005,8 @@ namespace Emby.Server.Implementations.Library
|
|||||||
return GetNewItemIdInternal(path, typeof(T), forceCaseInsensitiveId);
|
return GetNewItemIdInternal(path, typeof(T), forceCaseInsensitiveId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc />
|
||||||
/// Validate and refresh the People sub-set of the IBN.
|
public Task ValidatePeopleAsync(IProgress<double> progress, CancellationToken cancellationToken)
|
||||||
/// The items are stored in the db but not loaded into memory until actually requested by an operation.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="cancellationToken">The cancellation token.</param>
|
|
||||||
/// <param name="progress">The progress.</param>
|
|
||||||
/// <returns>Task.</returns>
|
|
||||||
public Task ValidatePeople(CancellationToken cancellationToken, IProgress<double> progress)
|
|
||||||
{
|
{
|
||||||
// Ensure the location is available.
|
// Ensure the location is available.
|
||||||
Directory.CreateDirectory(_configurationManager.ApplicationPaths.PeoplePath);
|
Directory.CreateDirectory(_configurationManager.ApplicationPaths.PeoplePath);
|
||||||
@@ -1034,15 +1028,6 @@ namespace Emby.Server.Implementations.Library
|
|||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Queues the library scan.
|
|
||||||
/// </summary>
|
|
||||||
public void QueueLibraryScan()
|
|
||||||
{
|
|
||||||
// Just run the scheduled task so that the user can see it
|
|
||||||
_taskManager.QueueScheduledTask<RefreshMediaLibraryTask>();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Validates the media library internal.
|
/// Validates the media library internal.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -1268,7 +1253,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
/// <exception cref="ArgumentNullException"><paramref name="id"/> is <c>null</c>.</exception>
|
/// <exception cref="ArgumentNullException"><paramref name="id"/> is <c>null</c>.</exception>
|
||||||
public BaseItem GetItemById(Guid id)
|
public BaseItem GetItemById(Guid id)
|
||||||
{
|
{
|
||||||
if (id == Guid.Empty)
|
if (id.Equals(default))
|
||||||
{
|
{
|
||||||
throw new ArgumentException("Guid can't be empty", nameof(id));
|
throw new ArgumentException("Guid can't be empty", nameof(id));
|
||||||
}
|
}
|
||||||
@@ -1290,7 +1275,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
|
|
||||||
public List<BaseItem> GetItemList(InternalItemsQuery query, bool allowExternalContent)
|
public List<BaseItem> GetItemList(InternalItemsQuery query, bool allowExternalContent)
|
||||||
{
|
{
|
||||||
if (query.Recursive && query.ParentId != Guid.Empty)
|
if (query.Recursive && !query.ParentId.Equals(default))
|
||||||
{
|
{
|
||||||
var parent = GetItemById(query.ParentId);
|
var parent = GetItemById(query.ParentId);
|
||||||
if (parent != null)
|
if (parent != null)
|
||||||
@@ -1314,7 +1299,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
|
|
||||||
public int GetCount(InternalItemsQuery query)
|
public int GetCount(InternalItemsQuery query)
|
||||||
{
|
{
|
||||||
if (query.Recursive && !query.ParentId.Equals(Guid.Empty))
|
if (query.Recursive && !query.ParentId.Equals(default))
|
||||||
{
|
{
|
||||||
var parent = GetItemById(query.ParentId);
|
var parent = GetItemById(query.ParentId);
|
||||||
if (parent != null)
|
if (parent != null)
|
||||||
@@ -1358,10 +1343,10 @@ namespace Emby.Server.Implementations.Library
|
|||||||
return _itemRepository.GetItems(query);
|
return _itemRepository.GetItems(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new QueryResult<BaseItem>
|
return new QueryResult<BaseItem>(
|
||||||
{
|
query.StartIndex,
|
||||||
Items = _itemRepository.GetItemList(query)
|
null,
|
||||||
};
|
_itemRepository.GetItemList(query));
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Guid> GetItemIds(InternalItemsQuery query)
|
public List<Guid> GetItemIds(InternalItemsQuery query)
|
||||||
@@ -1374,7 +1359,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
return _itemRepository.GetItemIdsList(query);
|
return _itemRepository.GetItemIdsList(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
public QueryResult<(BaseItem, ItemCounts)> GetStudios(InternalItemsQuery query)
|
public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetStudios(InternalItemsQuery query)
|
||||||
{
|
{
|
||||||
if (query.User != null)
|
if (query.User != null)
|
||||||
{
|
{
|
||||||
@@ -1385,7 +1370,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
return _itemRepository.GetStudios(query);
|
return _itemRepository.GetStudios(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
public QueryResult<(BaseItem, ItemCounts)> GetGenres(InternalItemsQuery query)
|
public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetGenres(InternalItemsQuery query)
|
||||||
{
|
{
|
||||||
if (query.User != null)
|
if (query.User != null)
|
||||||
{
|
{
|
||||||
@@ -1396,7 +1381,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
return _itemRepository.GetGenres(query);
|
return _itemRepository.GetGenres(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
public QueryResult<(BaseItem, ItemCounts)> GetMusicGenres(InternalItemsQuery query)
|
public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetMusicGenres(InternalItemsQuery query)
|
||||||
{
|
{
|
||||||
if (query.User != null)
|
if (query.User != null)
|
||||||
{
|
{
|
||||||
@@ -1407,7 +1392,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
return _itemRepository.GetMusicGenres(query);
|
return _itemRepository.GetMusicGenres(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
public QueryResult<(BaseItem, ItemCounts)> GetAllArtists(InternalItemsQuery query)
|
public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetAllArtists(InternalItemsQuery query)
|
||||||
{
|
{
|
||||||
if (query.User != null)
|
if (query.User != null)
|
||||||
{
|
{
|
||||||
@@ -1418,7 +1403,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
return _itemRepository.GetAllArtists(query);
|
return _itemRepository.GetAllArtists(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
public QueryResult<(BaseItem, ItemCounts)> GetArtists(InternalItemsQuery query)
|
public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetArtists(InternalItemsQuery query)
|
||||||
{
|
{
|
||||||
if (query.User != null)
|
if (query.User != null)
|
||||||
{
|
{
|
||||||
@@ -1442,7 +1427,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
for (int i = 0; i < len; i++)
|
for (int i = 0; i < len; i++)
|
||||||
{
|
{
|
||||||
parents[i] = GetItemById(ancestorIds[i]);
|
parents[i] = GetItemById(ancestorIds[i]);
|
||||||
if (!(parents[i] is ICollectionFolder || parents[i] is UserView))
|
if (parents[i] is not (ICollectionFolder or UserView))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1459,7 +1444,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public QueryResult<(BaseItem, ItemCounts)> GetAlbumArtists(InternalItemsQuery query)
|
public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetAlbumArtists(InternalItemsQuery query)
|
||||||
{
|
{
|
||||||
if (query.User != null)
|
if (query.User != null)
|
||||||
{
|
{
|
||||||
@@ -1472,7 +1457,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
|
|
||||||
public QueryResult<BaseItem> GetItemsResult(InternalItemsQuery query)
|
public QueryResult<BaseItem> GetItemsResult(InternalItemsQuery query)
|
||||||
{
|
{
|
||||||
if (query.Recursive && !query.ParentId.Equals(Guid.Empty))
|
if (query.Recursive && !query.ParentId.Equals(default))
|
||||||
{
|
{
|
||||||
var parent = GetItemById(query.ParentId);
|
var parent = GetItemById(query.ParentId);
|
||||||
if (parent != null)
|
if (parent != null)
|
||||||
@@ -1491,10 +1476,10 @@ namespace Emby.Server.Implementations.Library
|
|||||||
return _itemRepository.GetItems(query);
|
return _itemRepository.GetItems(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new QueryResult<BaseItem>
|
return new QueryResult<BaseItem>(
|
||||||
{
|
query.StartIndex,
|
||||||
Items = _itemRepository.GetItemList(query)
|
null,
|
||||||
};
|
_itemRepository.GetItemList(query));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetTopParentIdsOrAncestors(InternalItemsQuery query, List<BaseItem> parents)
|
private void SetTopParentIdsOrAncestors(InternalItemsQuery query, List<BaseItem> parents)
|
||||||
@@ -1528,7 +1513,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
private void AddUserToQuery(InternalItemsQuery query, User user, bool allowExternalContent = true)
|
private void AddUserToQuery(InternalItemsQuery query, User user, bool allowExternalContent = true)
|
||||||
{
|
{
|
||||||
if (query.AncestorIds.Length == 0 &&
|
if (query.AncestorIds.Length == 0 &&
|
||||||
query.ParentId.Equals(Guid.Empty) &&
|
query.ParentId.Equals(default) &&
|
||||||
query.ChannelIds.Count == 0 &&
|
query.ChannelIds.Count == 0 &&
|
||||||
query.TopParentIds.Length == 0 &&
|
query.TopParentIds.Length == 0 &&
|
||||||
string.IsNullOrEmpty(query.AncestorWithPresentationUniqueKey) &&
|
string.IsNullOrEmpty(query.AncestorWithPresentationUniqueKey) &&
|
||||||
@@ -1556,7 +1541,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Translate view into folders
|
// Translate view into folders
|
||||||
if (!view.DisplayParentId.Equals(Guid.Empty))
|
if (!view.DisplayParentId.Equals(default))
|
||||||
{
|
{
|
||||||
var displayParent = GetItemById(view.DisplayParentId);
|
var displayParent = GetItemById(view.DisplayParentId);
|
||||||
if (displayParent != null)
|
if (displayParent != null)
|
||||||
@@ -1567,7 +1552,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
return Array.Empty<Guid>();
|
return Array.Empty<Guid>();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!view.ParentId.Equals(Guid.Empty))
|
if (!view.ParentId.Equals(default))
|
||||||
{
|
{
|
||||||
var displayParent = GetItemById(view.ParentId);
|
var displayParent = GetItemById(view.ParentId);
|
||||||
if (displayParent != null)
|
if (displayParent != null)
|
||||||
@@ -1648,27 +1633,6 @@ namespace Emby.Server.Implementations.Library
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets all intro files.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>IEnumerable{System.String}.</returns>
|
|
||||||
public IEnumerable<string> GetAllIntroFiles()
|
|
||||||
{
|
|
||||||
return IntroProviders.SelectMany(i =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return i.GetAllIntroFiles().ToList();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "Error getting intro files");
|
|
||||||
|
|
||||||
return new List<string>();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Resolves the intro.
|
/// Resolves the intro.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -1758,7 +1722,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
return orderedItems ?? items;
|
return orderedItems ?? items;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<BaseItem> Sort(IEnumerable<BaseItem> items, User user, IEnumerable<ValueTuple<string, SortOrder>> orderBy)
|
public IEnumerable<BaseItem> Sort(IEnumerable<BaseItem> items, User user, IEnumerable<(string OrderBy, SortOrder SortOrder)> orderBy)
|
||||||
{
|
{
|
||||||
var isFirst = true;
|
var isFirst = true;
|
||||||
|
|
||||||
@@ -2012,16 +1976,16 @@ namespace Emby.Server.Implementations.Library
|
|||||||
public Task UpdateItemAsync(BaseItem item, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken)
|
public Task UpdateItemAsync(BaseItem item, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken)
|
||||||
=> UpdateItemsAsync(new[] { item }, parent, updateReason, cancellationToken);
|
=> UpdateItemsAsync(new[] { item }, parent, updateReason, cancellationToken);
|
||||||
|
|
||||||
public Task RunMetadataSavers(BaseItem item, ItemUpdateType updateReason)
|
public async Task RunMetadataSavers(BaseItem item, ItemUpdateType updateReason)
|
||||||
{
|
{
|
||||||
if (item.IsFileProtocol)
|
if (item.IsFileProtocol)
|
||||||
{
|
{
|
||||||
ProviderManager.SaveMetadata(item, updateReason);
|
await ProviderManager.SaveMetadataAsync(item, updateReason).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
item.DateLastSaved = DateTime.UtcNow;
|
item.DateLastSaved = DateTime.UtcNow;
|
||||||
|
|
||||||
return UpdateImagesAsync(item, updateReason >= ItemUpdateType.ImageUpdate);
|
await UpdateImagesAsync(item, updateReason >= ItemUpdateType.ImageUpdate).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -2190,7 +2154,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (!item.ParentId.Equals(Guid.Empty))
|
while (!item.ParentId.Equals(default))
|
||||||
{
|
{
|
||||||
var parent = item.GetParent();
|
var parent = item.GetParent();
|
||||||
if (parent == null || parent is AggregateFolder)
|
if (parent == null || parent is AggregateFolder)
|
||||||
@@ -2268,7 +2232,9 @@ namespace Emby.Server.Implementations.Library
|
|||||||
string viewType,
|
string viewType,
|
||||||
string sortName)
|
string sortName)
|
||||||
{
|
{
|
||||||
var parentIdString = parentId.Equals(Guid.Empty) ? null : parentId.ToString("N", CultureInfo.InvariantCulture);
|
var parentIdString = parentId.Equals(default)
|
||||||
|
? null
|
||||||
|
: parentId.ToString("N", CultureInfo.InvariantCulture);
|
||||||
var idValues = "38_namedview_" + name + user.Id.ToString("N", CultureInfo.InvariantCulture) + (parentIdString ?? string.Empty) + (viewType ?? string.Empty);
|
var idValues = "38_namedview_" + name + user.Id.ToString("N", CultureInfo.InvariantCulture) + (parentIdString ?? string.Empty) + (viewType ?? string.Empty);
|
||||||
|
|
||||||
var id = GetNewItemId(idValues, typeof(UserView));
|
var id = GetNewItemId(idValues, typeof(UserView));
|
||||||
@@ -2302,7 +2268,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
|
|
||||||
var refresh = isNew || DateTime.UtcNow - item.DateLastRefreshed >= _viewRefreshInterval;
|
var refresh = isNew || DateTime.UtcNow - item.DateLastRefreshed >= _viewRefreshInterval;
|
||||||
|
|
||||||
if (!refresh && !item.DisplayParentId.Equals(Guid.Empty))
|
if (!refresh && !item.DisplayParentId.Equals(default))
|
||||||
{
|
{
|
||||||
var displayParent = GetItemById(item.DisplayParentId);
|
var displayParent = GetItemById(item.DisplayParentId);
|
||||||
refresh = displayParent != null && displayParent.DateLastSaved > item.DateLastRefreshed;
|
refresh = displayParent != null && displayParent.DateLastSaved > item.DateLastRefreshed;
|
||||||
@@ -2369,7 +2335,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
|
|
||||||
var refresh = isNew || DateTime.UtcNow - item.DateLastRefreshed >= _viewRefreshInterval;
|
var refresh = isNew || DateTime.UtcNow - item.DateLastRefreshed >= _viewRefreshInterval;
|
||||||
|
|
||||||
if (!refresh && !item.DisplayParentId.Equals(Guid.Empty))
|
if (!refresh && !item.DisplayParentId.Equals(default))
|
||||||
{
|
{
|
||||||
var displayParent = GetItemById(item.DisplayParentId);
|
var displayParent = GetItemById(item.DisplayParentId);
|
||||||
refresh = displayParent != null && displayParent.DateLastSaved > item.DateLastRefreshed;
|
refresh = displayParent != null && displayParent.DateLastSaved > item.DateLastRefreshed;
|
||||||
@@ -2402,7 +2368,9 @@ namespace Emby.Server.Implementations.Library
|
|||||||
throw new ArgumentNullException(nameof(name));
|
throw new ArgumentNullException(nameof(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
var parentIdString = parentId.Equals(Guid.Empty) ? null : parentId.ToString("N", CultureInfo.InvariantCulture);
|
var parentIdString = parentId.Equals(default)
|
||||||
|
? null
|
||||||
|
: parentId.ToString("N", CultureInfo.InvariantCulture);
|
||||||
var idValues = "37_namedview_" + name + (parentIdString ?? string.Empty) + (viewType ?? string.Empty);
|
var idValues = "37_namedview_" + name + (parentIdString ?? string.Empty) + (viewType ?? string.Empty);
|
||||||
if (!string.IsNullOrEmpty(uniqueId))
|
if (!string.IsNullOrEmpty(uniqueId))
|
||||||
{
|
{
|
||||||
@@ -2446,7 +2414,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
|
|
||||||
var refresh = isNew || DateTime.UtcNow - item.DateLastRefreshed >= _viewRefreshInterval;
|
var refresh = isNew || DateTime.UtcNow - item.DateLastRefreshed >= _viewRefreshInterval;
|
||||||
|
|
||||||
if (!refresh && !item.DisplayParentId.Equals(Guid.Empty))
|
if (!refresh && !item.DisplayParentId.Equals(default))
|
||||||
{
|
{
|
||||||
var displayParent = GetItemById(item.DisplayParentId);
|
var displayParent = GetItemById(item.DisplayParentId);
|
||||||
refresh = displayParent != null && displayParent.DateLastSaved > item.DateLastRefreshed;
|
refresh = displayParent != null && displayParent.DateLastSaved > item.DateLastRefreshed;
|
||||||
@@ -2467,24 +2435,6 @@ namespace Emby.Server.Implementations.Library
|
|||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddExternalSubtitleStreams(
|
|
||||||
List<MediaStream> streams,
|
|
||||||
string videoPath,
|
|
||||||
string[] files)
|
|
||||||
{
|
|
||||||
new SubtitleResolver(BaseItem.LocalizationManager).AddExternalSubtitleStreams(streams, videoPath, streams.Count, files);
|
|
||||||
}
|
|
||||||
|
|
||||||
public BaseItem GetParentItem(string parentId, Guid? userId)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(parentId))
|
|
||||||
{
|
|
||||||
return GetParentItem((Guid?)null, userId);
|
|
||||||
}
|
|
||||||
|
|
||||||
return GetParentItem(new Guid(parentId), userId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public BaseItem GetParentItem(Guid? parentId, Guid? userId)
|
public BaseItem GetParentItem(Guid? parentId, Guid? userId)
|
||||||
{
|
{
|
||||||
if (parentId.HasValue)
|
if (parentId.HasValue)
|
||||||
@@ -2492,7 +2442,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
return GetItemById(parentId.Value);
|
return GetItemById(parentId.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userId.HasValue && userId != Guid.Empty)
|
if (userId.HasValue && !userId.Equals(default))
|
||||||
{
|
{
|
||||||
return GetUserRootFolder();
|
return GetUserRootFolder();
|
||||||
}
|
}
|
||||||
@@ -2500,16 +2450,6 @@ namespace Emby.Server.Implementations.Library
|
|||||||
return RootFolder;
|
return RootFolder;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public bool IsVideoFile(string path)
|
|
||||||
{
|
|
||||||
return VideoResolver.IsVideoFile(path, GetNamingOptions());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public bool IsAudioFile(string path)
|
|
||||||
=> AudioFileParser.IsAudioFile(path, GetNamingOptions());
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public int? GetSeasonNumberFromPath(string path)
|
public int? GetSeasonNumberFromPath(string path)
|
||||||
=> SeasonPathParser.Parse(path, true, true).SeasonNumber;
|
=> SeasonPathParser.Parse(path, true, true).SeasonNumber;
|
||||||
@@ -2525,7 +2465,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
isAbsoluteNaming = null;
|
isAbsoluteNaming = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var resolver = new EpisodeResolver(GetNamingOptions());
|
var resolver = new EpisodeResolver(_namingOptions);
|
||||||
|
|
||||||
var isFolder = episode.VideoType == VideoType.BluRay || episode.VideoType == VideoType.Dvd;
|
var isFolder = episode.VideoType == VideoType.BluRay || episode.VideoType == VideoType.Dvd;
|
||||||
|
|
||||||
@@ -2682,113 +2622,77 @@ namespace Emby.Server.Implementations.Library
|
|||||||
return changed;
|
return changed;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public NamingOptions GetNamingOptions()
|
|
||||||
{
|
|
||||||
if (_namingOptions == null)
|
|
||||||
{
|
|
||||||
_namingOptions = new NamingOptions();
|
|
||||||
_videoFileExtensions = _namingOptions.VideoFileExtensions;
|
|
||||||
}
|
|
||||||
|
|
||||||
return _namingOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ItemLookupInfo ParseName(string name)
|
public ItemLookupInfo ParseName(string name)
|
||||||
{
|
{
|
||||||
var namingOptions = GetNamingOptions();
|
var namingOptions = _namingOptions;
|
||||||
var result = VideoResolver.CleanDateTime(name, namingOptions);
|
var result = VideoResolver.CleanDateTime(name, namingOptions);
|
||||||
|
|
||||||
return new ItemLookupInfo
|
return new ItemLookupInfo
|
||||||
{
|
{
|
||||||
Name = VideoResolver.TryCleanString(result.Name, namingOptions, out var newName) ? newName.ToString() : result.Name,
|
Name = VideoResolver.TryCleanString(result.Name, namingOptions, out var newName) ? newName : result.Name,
|
||||||
Year = result.Year
|
Year = result.Year
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<Video> FindTrailers(BaseItem owner, List<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService)
|
public IEnumerable<BaseItem> FindExtras(BaseItem owner, IReadOnlyList<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService)
|
||||||
{
|
{
|
||||||
var namingOptions = GetNamingOptions();
|
var ownerVideoInfo = VideoResolver.Resolve(owner.Path, owner.IsFolder, _namingOptions);
|
||||||
|
if (ownerVideoInfo == null)
|
||||||
var files = owner.IsInMixedFolder ? new List<FileSystemMetadata>() : fileSystemChildren.Where(i => i.IsDirectory)
|
|
||||||
.Where(i => string.Equals(i.Name, BaseItem.TrailersFolderName, StringComparison.OrdinalIgnoreCase))
|
|
||||||
.SelectMany(i => _fileSystem.GetFiles(i.FullName, _videoFileExtensions, false, false))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
var videos = VideoListResolver.Resolve(fileSystemChildren, namingOptions);
|
|
||||||
|
|
||||||
var currentVideo = videos.FirstOrDefault(i => string.Equals(owner.Path, i.Files[0].Path, StringComparison.OrdinalIgnoreCase));
|
|
||||||
|
|
||||||
if (currentVideo != null)
|
|
||||||
{
|
{
|
||||||
files.AddRange(currentVideo.Extras.Where(i => i.ExtraType == ExtraType.Trailer).Select(i => _fileSystem.GetFileInfo(i.Path)));
|
yield break;
|
||||||
}
|
}
|
||||||
|
|
||||||
var resolvers = new IItemResolver[]
|
var count = fileSystemChildren.Count;
|
||||||
|
for (var i = 0; i < count; i++)
|
||||||
{
|
{
|
||||||
new GenericVideoResolver<Trailer>(this)
|
var current = fileSystemChildren[i];
|
||||||
};
|
if (current.IsDirectory && _namingOptions.AllExtrasTypesFolderNames.ContainsKey(current.Name))
|
||||||
|
|
||||||
return ResolvePaths(files, directoryService, null, new LibraryOptions(), null, resolvers)
|
|
||||||
.OfType<Trailer>()
|
|
||||||
.Select(video =>
|
|
||||||
{
|
{
|
||||||
// Try to retrieve it from the db. If we don't find it, use the resolved version
|
var filesInSubFolder = _fileSystem.GetFiles(current.FullName, null, false, false);
|
||||||
if (GetItemById(video.Id) is Trailer dbItem)
|
foreach (var file in filesInSubFolder)
|
||||||
{
|
{
|
||||||
video = dbItem;
|
if (!_extraResolver.TryGetExtraTypeForOwner(file.FullName, ownerVideoInfo, out var extraType))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var extra = GetExtra(file, extraType.Value);
|
||||||
|
if (extra != null)
|
||||||
|
{
|
||||||
|
yield return extra;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
video.ParentId = Guid.Empty;
|
else if (!current.IsDirectory && _extraResolver.TryGetExtraTypeForOwner(current.FullName, ownerVideoInfo, out var extraType))
|
||||||
video.OwnerId = owner.Id;
|
{
|
||||||
video.ExtraType = ExtraType.Trailer;
|
var extra = GetExtra(current, extraType.Value);
|
||||||
video.TrailerTypes = new[] { TrailerType.LocalTrailer };
|
if (extra != null)
|
||||||
|
{
|
||||||
return video;
|
yield return extra;
|
||||||
|
}
|
||||||
// Sort them so that the list can be easily compared for changes
|
}
|
||||||
}).OrderBy(i => i.Path);
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerable<Video> FindExtras(BaseItem owner, List<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService)
|
|
||||||
{
|
|
||||||
var namingOptions = GetNamingOptions();
|
|
||||||
|
|
||||||
var files = owner.IsInMixedFolder ? new List<FileSystemMetadata>() : fileSystemChildren.Where(i => i.IsDirectory)
|
|
||||||
.Where(i => BaseItem.AllExtrasTypesFolderNames.ContainsKey(i.Name ?? string.Empty))
|
|
||||||
.SelectMany(i => _fileSystem.GetFiles(i.FullName, _videoFileExtensions, false, false))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
var videos = VideoListResolver.Resolve(fileSystemChildren, namingOptions);
|
|
||||||
|
|
||||||
var currentVideo = videos.FirstOrDefault(i => string.Equals(owner.Path, i.Files[0].Path, StringComparison.OrdinalIgnoreCase));
|
|
||||||
|
|
||||||
if (currentVideo != null)
|
|
||||||
{
|
|
||||||
files.AddRange(currentVideo.Extras.Where(i => i.ExtraType != ExtraType.Trailer).Select(i => _fileSystem.GetFileInfo(i.Path)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ResolvePaths(files, directoryService, null, new LibraryOptions(), null)
|
BaseItem GetExtra(FileSystemMetadata file, ExtraType extraType)
|
||||||
.OfType<Video>()
|
{
|
||||||
.Select(video =>
|
var extra = ResolvePath(_fileSystem.GetFileInfo(file.FullName), directoryService, _extraResolver.GetResolversForExtraType(extraType));
|
||||||
|
if (extra is not Video && extra is not Audio)
|
||||||
{
|
{
|
||||||
// Try to retrieve it from the db. If we don't find it, use the resolved version
|
return null;
|
||||||
var dbItem = GetItemById(video.Id) as Video;
|
}
|
||||||
|
|
||||||
if (dbItem != null)
|
// Try to retrieve it from the db. If we don't find it, use the resolved version
|
||||||
{
|
var itemById = GetItemById(extra.Id);
|
||||||
video = dbItem;
|
if (itemById != null)
|
||||||
}
|
{
|
||||||
|
extra = itemById;
|
||||||
|
}
|
||||||
|
|
||||||
video.ParentId = Guid.Empty;
|
extra.ExtraType = extraType;
|
||||||
video.OwnerId = owner.Id;
|
extra.ParentId = Guid.Empty;
|
||||||
|
extra.OwnerId = owner.Id;
|
||||||
SetExtraTypeFromFilename(video);
|
return extra;
|
||||||
|
}
|
||||||
return video;
|
|
||||||
|
|
||||||
// Sort them so that the list can be easily compared for changes
|
|
||||||
}).OrderBy(i => i.Path);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetPathAfterNetworkSubstitution(string path, BaseItem ownerItem)
|
public string GetPathAfterNetworkSubstitution(string path, BaseItem ownerItem)
|
||||||
@@ -2828,25 +2732,6 @@ namespace Emby.Server.Implementations.Library
|
|||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string SubstitutePath(string path, string from, string to)
|
|
||||||
{
|
|
||||||
if (path.TryReplaceSubPath(from, to, out var newPath))
|
|
||||||
{
|
|
||||||
return newPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SetExtraTypeFromFilename(Video item)
|
|
||||||
{
|
|
||||||
var resolver = new ExtraResolver(GetNamingOptions());
|
|
||||||
|
|
||||||
var result = resolver.GetExtraInfo(item.Path);
|
|
||||||
|
|
||||||
item.ExtraType = result.ExtraType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<PersonInfo> GetPeople(InternalPeopleQuery query)
|
public List<PersonInfo> GetPeople(InternalPeopleQuery query)
|
||||||
{
|
{
|
||||||
return _itemRepository.GetPeople(query);
|
return _itemRepository.GetPeople(query);
|
||||||
@@ -2953,10 +2838,13 @@ namespace Emby.Server.Implementations.Library
|
|||||||
|
|
||||||
var rootFolderPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath;
|
var rootFolderPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath;
|
||||||
|
|
||||||
|
var existingNameCount = 1; // first numbered name will be 2
|
||||||
var virtualFolderPath = Path.Combine(rootFolderPath, name);
|
var virtualFolderPath = Path.Combine(rootFolderPath, name);
|
||||||
|
var originalName = name;
|
||||||
while (Directory.Exists(virtualFolderPath))
|
while (Directory.Exists(virtualFolderPath))
|
||||||
{
|
{
|
||||||
name += "1";
|
existingNameCount++;
|
||||||
|
name = originalName + existingNameCount;
|
||||||
virtualFolderPath = Path.Combine(rootFolderPath, name);
|
virtualFolderPath = Path.Combine(rootFolderPath, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3059,7 +2947,10 @@ namespace Emby.Server.Implementations.Library
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CreateItems(personsToSave, null, CancellationToken.None);
|
if (personsToSave.Count > 0)
|
||||||
|
{
|
||||||
|
CreateItems(personsToSave, null, CancellationToken.None);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void StartScanInBackground()
|
private void StartScanInBackground()
|
||||||
|
|||||||
@@ -66,11 +66,8 @@ namespace Emby.Server.Implementations.Library
|
|||||||
{
|
{
|
||||||
var delayMs = mediaSource.AnalyzeDurationMs ?? 0;
|
var delayMs = mediaSource.AnalyzeDurationMs ?? 0;
|
||||||
delayMs = Math.Max(3000, delayMs);
|
delayMs = Math.Max(3000, delayMs);
|
||||||
if (delayMs > 0)
|
_logger.LogInformation("Waiting {0}ms before probing the live stream", delayMs);
|
||||||
{
|
await Task.Delay(delayMs, cancellationToken).ConfigureAwait(false);
|
||||||
_logger.LogInformation("Waiting {0}ms before probing the live stream", delayMs);
|
|
||||||
await Task.Delay(delayMs, cancellationToken).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mediaSource.AnalyzeDurationMs = 3000;
|
mediaSource.AnalyzeDurationMs = 3000;
|
||||||
|
|||||||
@@ -172,24 +172,16 @@ namespace Emby.Server.Implementations.Library
|
|||||||
|
|
||||||
foreach (var source in dynamicMediaSources)
|
foreach (var source in dynamicMediaSources)
|
||||||
{
|
{
|
||||||
if (user != null)
|
|
||||||
{
|
|
||||||
SetDefaultAudioAndSubtitleStreamIndexes(item, source, user);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate that this is actually possible
|
// Validate that this is actually possible
|
||||||
if (source.SupportsDirectStream)
|
if (source.SupportsDirectStream)
|
||||||
{
|
{
|
||||||
source.SupportsDirectStream = SupportsDirectStream(source.Path, source.Protocol);
|
source.SupportsDirectStream = SupportsDirectStream(source.Path, source.Protocol);
|
||||||
}
|
}
|
||||||
|
|
||||||
list.Add(source);
|
if (user != null)
|
||||||
}
|
|
||||||
|
|
||||||
if (user != null)
|
|
||||||
{
|
|
||||||
foreach (var source in list)
|
|
||||||
{
|
{
|
||||||
|
SetDefaultAudioAndSubtitleStreamIndexes(item, source, user);
|
||||||
|
|
||||||
if (string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
source.SupportsTranscoding = user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding);
|
source.SupportsTranscoding = user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding);
|
||||||
@@ -200,6 +192,8 @@ namespace Emby.Server.Implementations.Library
|
|||||||
source.SupportsDirectStream = user.HasPermission(PermissionKind.EnablePlaybackRemuxing);
|
source.SupportsDirectStream = user.HasPermission(PermissionKind.EnablePlaybackRemuxing);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
list.Add(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
return SortMediaSources(list);
|
return SortMediaSources(list);
|
||||||
@@ -338,13 +332,23 @@ namespace Emby.Server.Implementations.Library
|
|||||||
foreach (var source in sources)
|
foreach (var source in sources)
|
||||||
{
|
{
|
||||||
SetDefaultAudioAndSubtitleStreamIndexes(item, source, user);
|
SetDefaultAudioAndSubtitleStreamIndexes(item, source, user);
|
||||||
|
|
||||||
|
if (string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
source.SupportsTranscoding = user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding);
|
||||||
|
}
|
||||||
|
else if (string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
source.SupportsTranscoding = user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding);
|
||||||
|
source.SupportsDirectStream = user.HasPermission(PermissionKind.EnablePlaybackRemuxing);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return sources;
|
return sources;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string[] NormalizeLanguage(string language)
|
private IReadOnlyList<string> NormalizeLanguage(string language)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(language))
|
if (string.IsNullOrEmpty(language))
|
||||||
{
|
{
|
||||||
@@ -464,12 +468,11 @@ namespace Emby.Server.Implementations.Library
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var tuple = GetProvider(request.OpenToken);
|
var (provider, keyId) = GetProvider(request.OpenToken);
|
||||||
var provider = tuple.Item1;
|
|
||||||
|
|
||||||
var currentLiveStreams = _openStreams.Values.ToList();
|
var currentLiveStreams = _openStreams.Values.ToList();
|
||||||
|
|
||||||
liveStream = await provider.OpenMediaSource(tuple.Item2, currentLiveStreams, cancellationToken).ConfigureAwait(false);
|
liveStream = await provider.OpenMediaSource(keyId, currentLiveStreams, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
mediaSource = liveStream.MediaSource;
|
mediaSource = liveStream.MediaSource;
|
||||||
|
|
||||||
@@ -515,10 +518,10 @@ namespace Emby.Server.Implementations.Library
|
|||||||
_logger.LogInformation("Live stream opened: {@MediaSource}", mediaSource);
|
_logger.LogInformation("Live stream opened: {@MediaSource}", mediaSource);
|
||||||
var clone = JsonSerializer.Deserialize<MediaSourceInfo>(json, _jsonOptions);
|
var clone = JsonSerializer.Deserialize<MediaSourceInfo>(json, _jsonOptions);
|
||||||
|
|
||||||
if (!request.UserId.Equals(Guid.Empty))
|
if (!request.UserId.Equals(default))
|
||||||
{
|
{
|
||||||
var user = _userManager.GetUserById(request.UserId);
|
var user = _userManager.GetUserById(request.UserId);
|
||||||
var item = request.ItemId.Equals(Guid.Empty)
|
var item = request.ItemId.Equals(default)
|
||||||
? null
|
? null
|
||||||
: _libraryManager.GetItemById(request.ItemId);
|
: _libraryManager.GetItemById(request.ItemId);
|
||||||
SetDefaultAudioAndSubtitleStreamIndexes(item, clone, user);
|
SetDefaultAudioAndSubtitleStreamIndexes(item, clone, user);
|
||||||
@@ -829,7 +832,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private (IMediaSourceProvider, string) GetProvider(string key)
|
private (IMediaSourceProvider MediaSourceProvider, string KeyId) GetProvider(string key)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(key))
|
if (string.IsNullOrEmpty(key))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,21 +1,19 @@
|
|||||||
#nullable disable
|
|
||||||
|
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Jellyfin.Data.Enums;
|
using Jellyfin.Data.Enums;
|
||||||
|
using Jellyfin.Extensions;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Library
|
namespace Emby.Server.Implementations.Library
|
||||||
{
|
{
|
||||||
public static class MediaStreamSelector
|
public static class MediaStreamSelector
|
||||||
{
|
{
|
||||||
public static int? GetDefaultAudioStreamIndex(List<MediaStream> streams, string[] preferredLanguages, bool preferDefaultTrack)
|
public static int? GetDefaultAudioStreamIndex(IReadOnlyList<MediaStream> streams, IReadOnlyList<string> preferredLanguages, bool preferDefaultTrack)
|
||||||
{
|
{
|
||||||
streams = GetSortedStreams(streams, MediaStreamType.Audio, preferredLanguages)
|
var sortedStreams = GetSortedStreams(streams, MediaStreamType.Audio, preferredLanguages);
|
||||||
.ToList();
|
|
||||||
|
|
||||||
if (preferDefaultTrack)
|
if (preferDefaultTrack)
|
||||||
{
|
{
|
||||||
@@ -27,24 +25,15 @@ namespace Emby.Server.Implementations.Library
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var stream = streams.FirstOrDefault();
|
return sortedStreams.FirstOrDefault()?.Index;
|
||||||
|
|
||||||
if (stream != null)
|
|
||||||
{
|
|
||||||
return stream.Index;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int? GetDefaultSubtitleStreamIndex(
|
public static int? GetDefaultSubtitleStreamIndex(
|
||||||
IEnumerable<MediaStream> streams,
|
IEnumerable<MediaStream> streams,
|
||||||
string[] preferredLanguages,
|
IReadOnlyList<string> preferredLanguages,
|
||||||
SubtitlePlaybackMode mode,
|
SubtitlePlaybackMode mode,
|
||||||
string audioTrackLanguage)
|
string audioTrackLanguage)
|
||||||
{
|
{
|
||||||
MediaStream stream = null;
|
|
||||||
|
|
||||||
if (mode == SubtitlePlaybackMode.None)
|
if (mode == SubtitlePlaybackMode.None)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
@@ -58,24 +47,25 @@ namespace Emby.Server.Implementations.Library
|
|||||||
.ThenByDescending(x => x.IsDefault)
|
.ThenByDescending(x => x.IsDefault)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
|
MediaStream? stream = null;
|
||||||
if (mode == SubtitlePlaybackMode.Default)
|
if (mode == SubtitlePlaybackMode.Default)
|
||||||
{
|
{
|
||||||
// Prefer embedded metadata over smart logic
|
// Prefer embedded metadata over smart logic
|
||||||
stream = sortedStreams.FirstOrDefault(s => s.IsExternal || s.IsForced || s.IsDefault);
|
stream = sortedStreams.FirstOrDefault(s => s.IsExternal || s.IsForced || s.IsDefault);
|
||||||
|
|
||||||
// if the audio language is not understood by the user, load their preferred subs, if there are any
|
// if the audio language is not understood by the user, load their preferred subs, if there are any
|
||||||
if (stream == null && !preferredLanguages.Contains(audioTrackLanguage, StringComparer.OrdinalIgnoreCase))
|
if (stream == null && !preferredLanguages.Contains(audioTrackLanguage, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
stream = sortedStreams.FirstOrDefault(s => !s.IsForced && preferredLanguages.Contains(s.Language, StringComparer.OrdinalIgnoreCase));
|
stream = sortedStreams.FirstOrDefault(s => !s.IsForced && preferredLanguages.Contains(s.Language, StringComparison.OrdinalIgnoreCase));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (mode == SubtitlePlaybackMode.Smart)
|
else if (mode == SubtitlePlaybackMode.Smart)
|
||||||
{
|
{
|
||||||
// if the audio language is not understood by the user, load their preferred subs, if there are any
|
// if the audio language is not understood by the user, load their preferred subs, if there are any
|
||||||
if (!preferredLanguages.Contains(audioTrackLanguage, StringComparer.OrdinalIgnoreCase))
|
if (!preferredLanguages.Contains(audioTrackLanguage, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
stream = streams.FirstOrDefault(s => !s.IsForced && preferredLanguages.Contains(s.Language, StringComparer.OrdinalIgnoreCase)) ??
|
stream = streams.FirstOrDefault(s => !s.IsForced && preferredLanguages.Contains(s.Language, StringComparison.OrdinalIgnoreCase)) ??
|
||||||
streams.FirstOrDefault(s => preferredLanguages.Contains(s.Language, StringComparer.OrdinalIgnoreCase));
|
streams.FirstOrDefault(s => preferredLanguages.Contains(s.Language, StringComparison.OrdinalIgnoreCase));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (mode == SubtitlePlaybackMode.Always)
|
else if (mode == SubtitlePlaybackMode.Always)
|
||||||
@@ -94,26 +84,27 @@ namespace Emby.Server.Implementations.Library
|
|||||||
return stream?.Index;
|
return stream?.Index;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IEnumerable<MediaStream> GetSortedStreams(IEnumerable<MediaStream> streams, MediaStreamType type, string[] languagePreferences)
|
private static IEnumerable<MediaStream> GetSortedStreams(IEnumerable<MediaStream> streams, MediaStreamType type, IReadOnlyList<string> languagePreferences)
|
||||||
{
|
{
|
||||||
// Give some preference to external text subs for better performance
|
// Give some preference to external text subs for better performance
|
||||||
return streams.Where(i => i.Type == type)
|
return streams
|
||||||
|
.Where(i => i.Type == type)
|
||||||
.OrderBy(i =>
|
.OrderBy(i =>
|
||||||
{
|
{
|
||||||
var index = FindIndex(languagePreferences, i.Language);
|
var index = languagePreferences.FindIndex(x => string.Equals(x, i.Language, StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
return index == -1 ? 100 : index;
|
return index == -1 ? 100 : index;
|
||||||
})
|
})
|
||||||
.ThenBy(i => GetBooleanOrderBy(i.IsDefault))
|
.ThenBy(i => GetBooleanOrderBy(i.IsDefault))
|
||||||
.ThenBy(i => GetBooleanOrderBy(i.SupportsExternalStream))
|
.ThenBy(i => GetBooleanOrderBy(i.SupportsExternalStream))
|
||||||
.ThenBy(i => GetBooleanOrderBy(i.IsTextSubtitleStream))
|
.ThenBy(i => GetBooleanOrderBy(i.IsTextSubtitleStream))
|
||||||
.ThenBy(i => GetBooleanOrderBy(i.IsExternal))
|
.ThenBy(i => GetBooleanOrderBy(i.IsExternal))
|
||||||
.ThenBy(i => i.Index);
|
.ThenBy(i => i.Index);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void SetSubtitleStreamScores(
|
public static void SetSubtitleStreamScores(
|
||||||
List<MediaStream> streams,
|
IReadOnlyList<MediaStream> streams,
|
||||||
string[] preferredLanguages,
|
IReadOnlyList<string> preferredLanguages,
|
||||||
SubtitlePlaybackMode mode,
|
SubtitlePlaybackMode mode,
|
||||||
string audioTrackLanguage)
|
string audioTrackLanguage)
|
||||||
{
|
{
|
||||||
@@ -122,70 +113,52 @@ namespace Emby.Server.Implementations.Library
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
streams = GetSortedStreams(streams, MediaStreamType.Subtitle, preferredLanguages)
|
var sortedStreams = GetSortedStreams(streams, MediaStreamType.Subtitle, preferredLanguages);
|
||||||
.ToList();
|
|
||||||
|
|
||||||
var filteredStreams = new List<MediaStream>();
|
var filteredStreams = new List<MediaStream>();
|
||||||
|
|
||||||
if (mode == SubtitlePlaybackMode.Default)
|
if (mode == SubtitlePlaybackMode.Default)
|
||||||
{
|
{
|
||||||
// Prefer embedded metadata over smart logic
|
// Prefer embedded metadata over smart logic
|
||||||
filteredStreams = streams.Where(s => s.IsForced || s.IsDefault)
|
filteredStreams = sortedStreams.Where(s => s.IsForced || s.IsDefault)
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
else if (mode == SubtitlePlaybackMode.Smart)
|
else if (mode == SubtitlePlaybackMode.Smart)
|
||||||
{
|
{
|
||||||
// Prefer smart logic over embedded metadata
|
// Prefer smart logic over embedded metadata
|
||||||
if (!preferredLanguages.Contains(audioTrackLanguage, StringComparer.OrdinalIgnoreCase))
|
if (!preferredLanguages.Contains(audioTrackLanguage, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
filteredStreams = streams.Where(s => !s.IsForced && preferredLanguages.Contains(s.Language, StringComparer.OrdinalIgnoreCase))
|
filteredStreams = sortedStreams.Where(s => !s.IsForced && preferredLanguages.Contains(s.Language, StringComparison.OrdinalIgnoreCase))
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (mode == SubtitlePlaybackMode.Always)
|
else if (mode == SubtitlePlaybackMode.Always)
|
||||||
{
|
{
|
||||||
// always load the most suitable full subtitles
|
// always load the most suitable full subtitles
|
||||||
filteredStreams = streams.Where(s => !s.IsForced)
|
filteredStreams = sortedStreams.Where(s => !s.IsForced).ToList();
|
||||||
.ToList();
|
|
||||||
}
|
}
|
||||||
else if (mode == SubtitlePlaybackMode.OnlyForced)
|
else if (mode == SubtitlePlaybackMode.OnlyForced)
|
||||||
{
|
{
|
||||||
// always load the most suitable full subtitles
|
// always load the most suitable full subtitles
|
||||||
filteredStreams = streams.Where(s => s.IsForced).ToList();
|
filteredStreams = sortedStreams.Where(s => s.IsForced).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
// load forced subs if we have found no suitable full subtitles
|
// load forced subs if we have found no suitable full subtitles
|
||||||
if (filteredStreams.Count == 0)
|
var iterStreams = filteredStreams.Count == 0
|
||||||
{
|
? sortedStreams.Where(s => s.IsForced && string.Equals(s.Language, audioTrackLanguage, StringComparison.OrdinalIgnoreCase))
|
||||||
filteredStreams = streams
|
: filteredStreams;
|
||||||
.Where(s => s.IsForced && string.Equals(s.Language, audioTrackLanguage, StringComparison.OrdinalIgnoreCase))
|
|
||||||
.ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var stream in filteredStreams)
|
foreach (var stream in iterStreams)
|
||||||
{
|
{
|
||||||
stream.Score = GetSubtitleScore(stream, preferredLanguages);
|
stream.Score = GetSubtitleScore(stream, preferredLanguages);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int FindIndex(string[] list, string value)
|
private static int GetSubtitleScore(MediaStream stream, IReadOnlyList<string> languagePreferences)
|
||||||
{
|
|
||||||
for (var i = 0; i < list.Length; i++)
|
|
||||||
{
|
|
||||||
if (string.Equals(list[i], value, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int GetSubtitleScore(MediaStream stream, string[] languagePreferences)
|
|
||||||
{
|
{
|
||||||
var values = new List<int>();
|
var values = new List<int>();
|
||||||
|
|
||||||
var index = FindIndex(languagePreferences, stream.Language);
|
var index = languagePreferences.FindIndex(x => string.Equals(x, stream.Language, StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
values.Add(index == -1 ? 0 : 100 - index);
|
values.Add(index == -1 ? 0 : 100 - index);
|
||||||
|
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
var genres = item
|
var genres = item
|
||||||
.GetRecursiveChildren(user, new InternalItemsQuery(user)
|
.GetRecursiveChildren(user, new InternalItemsQuery(user)
|
||||||
{
|
{
|
||||||
IncludeItemTypes = new[] { nameof(Audio) },
|
IncludeItemTypes = new[] { BaseItemKind.Audio },
|
||||||
DtoOptions = dtoOptions
|
DtoOptions = dtoOptions
|
||||||
})
|
})
|
||||||
.Cast<Audio>()
|
.Cast<Audio>()
|
||||||
@@ -80,7 +80,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
{
|
{
|
||||||
return Guid.Empty;
|
return Guid.Empty;
|
||||||
}
|
}
|
||||||
}).Where(i => !i.Equals(Guid.Empty)).ToArray();
|
}).Where(i => !i.Equals(default)).ToArray();
|
||||||
|
|
||||||
return GetInstantMixFromGenreIds(genreIds, user, dtoOptions);
|
return GetInstantMixFromGenreIds(genreIds, user, dtoOptions);
|
||||||
}
|
}
|
||||||
@@ -89,7 +89,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
{
|
{
|
||||||
return _libraryManager.GetItemList(new InternalItemsQuery(user)
|
return _libraryManager.GetItemList(new InternalItemsQuery(user)
|
||||||
{
|
{
|
||||||
IncludeItemTypes = new[] { nameof(Audio) },
|
IncludeItemTypes = new[] { BaseItemKind.Audio },
|
||||||
|
|
||||||
GenreIds = genreIds.ToArray(),
|
GenreIds = genreIds.ToArray(),
|
||||||
|
|
||||||
@@ -103,7 +103,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
|
|
||||||
public List<BaseItem> GetInstantMixFromItem(BaseItem item, User user, DtoOptions dtoOptions)
|
public List<BaseItem> GetInstantMixFromItem(BaseItem item, User user, DtoOptions dtoOptions)
|
||||||
{
|
{
|
||||||
if (item is MusicGenre genre)
|
if (item is MusicGenre)
|
||||||
{
|
{
|
||||||
return GetInstantMixFromGenreIds(new[] { item.Id }, user, dtoOptions);
|
return GetInstantMixFromGenreIds(new[] { item.Id }, user, dtoOptions);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
/// <param name="attribute">The attrib.</param>
|
/// <param name="attribute">The attrib.</param>
|
||||||
/// <returns>System.String.</returns>
|
/// <returns>System.String.</returns>
|
||||||
/// <exception cref="ArgumentException"><paramref name="str" /> or <paramref name="attribute" /> is empty.</exception>
|
/// <exception cref="ArgumentException"><paramref name="str" /> or <paramref name="attribute" /> is empty.</exception>
|
||||||
public static string? GetAttributeValue(this string str, string attribute)
|
public static string? GetAttributeValue(this ReadOnlySpan<char> str, ReadOnlySpan<char> attribute)
|
||||||
{
|
{
|
||||||
if (str.Length == 0)
|
if (str.Length == 0)
|
||||||
{
|
{
|
||||||
@@ -28,17 +28,31 @@ namespace Emby.Server.Implementations.Library
|
|||||||
throw new ArgumentException("String can't be empty.", nameof(attribute));
|
throw new ArgumentException("String can't be empty.", nameof(attribute));
|
||||||
}
|
}
|
||||||
|
|
||||||
string srch = "[" + attribute + "=";
|
var attributeIndex = str.IndexOf(attribute, StringComparison.OrdinalIgnoreCase);
|
||||||
int start = str.IndexOf(srch, StringComparison.OrdinalIgnoreCase);
|
|
||||||
if (start != -1)
|
// Must be at least 3 characters after the attribute =, ], any character.
|
||||||
|
var maxIndex = str.Length - attribute.Length - 3;
|
||||||
|
while (attributeIndex > -1 && attributeIndex < maxIndex)
|
||||||
{
|
{
|
||||||
start += srch.Length;
|
var attributeEnd = attributeIndex + attribute.Length;
|
||||||
int end = str.IndexOf(']', start);
|
if (attributeIndex > 0
|
||||||
return str.Substring(start, end - start);
|
&& str[attributeIndex - 1] == '['
|
||||||
|
&& (str[attributeEnd] == '=' || str[attributeEnd] == '-'))
|
||||||
|
{
|
||||||
|
var closingIndex = str[attributeEnd..].IndexOf(']');
|
||||||
|
// Must be at least 1 character before the closing bracket.
|
||||||
|
if (closingIndex > 1)
|
||||||
|
{
|
||||||
|
return str[(attributeEnd + 1)..(attributeEnd + closingIndex)].Trim().ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
str = str[attributeEnd..];
|
||||||
|
attributeIndex = str.IndexOf(attribute, StringComparison.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
// for imdbid we also accept pattern matching
|
// for imdbid we also accept pattern matching
|
||||||
if (string.Equals(attribute, "imdbid", StringComparison.OrdinalIgnoreCase))
|
if (attribute.Equals("imdbid", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
var match = ProviderIdParsers.TryFindImdbId(str, out var imdbId);
|
var match = ProviderIdParsers.TryFindImdbId(str, out var imdbId);
|
||||||
return match ? imdbId.ToString() : null;
|
return match ? imdbId.ToString() : null;
|
||||||
|
|||||||
@@ -6,7 +6,10 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Emby.Naming.Audio;
|
||||||
using Emby.Naming.AudioBook;
|
using Emby.Naming.AudioBook;
|
||||||
|
using Emby.Naming.Common;
|
||||||
|
using Emby.Naming.Video;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.Providers;
|
using MediaBrowser.Controller.Providers;
|
||||||
@@ -21,11 +24,11 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class AudioResolver : ItemResolver<MediaBrowser.Controller.Entities.Audio.Audio>, IMultiItemResolver
|
public class AudioResolver : ItemResolver<MediaBrowser.Controller.Entities.Audio.Audio>, IMultiItemResolver
|
||||||
{
|
{
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly NamingOptions _namingOptions;
|
||||||
|
|
||||||
public AudioResolver(ILibraryManager libraryManager)
|
public AudioResolver(NamingOptions namingOptions)
|
||||||
{
|
{
|
||||||
_libraryManager = libraryManager;
|
_namingOptions = namingOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -40,7 +43,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
|||||||
string collectionType,
|
string collectionType,
|
||||||
IDirectoryService directoryService)
|
IDirectoryService directoryService)
|
||||||
{
|
{
|
||||||
var result = ResolveMultipleInternal(parent, files, collectionType, directoryService);
|
var result = ResolveMultipleInternal(parent, files, collectionType);
|
||||||
|
|
||||||
if (result != null)
|
if (result != null)
|
||||||
{
|
{
|
||||||
@@ -56,12 +59,11 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
|||||||
private MultiItemResolverResult ResolveMultipleInternal(
|
private MultiItemResolverResult ResolveMultipleInternal(
|
||||||
Folder parent,
|
Folder parent,
|
||||||
List<FileSystemMetadata> files,
|
List<FileSystemMetadata> files,
|
||||||
string collectionType,
|
string collectionType)
|
||||||
IDirectoryService directoryService)
|
|
||||||
{
|
{
|
||||||
if (string.Equals(collectionType, CollectionType.Books, StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(collectionType, CollectionType.Books, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return ResolveMultipleAudio<AudioBook>(parent, files, directoryService, false, collectionType, true);
|
return ResolveMultipleAudio(parent, files, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@@ -87,14 +89,10 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var files = args.FileSystemChildren
|
return FindAudioBook(args, false);
|
||||||
.Where(i => !_libraryManager.IgnoreFile(i, args.Parent))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
return FindAudio<AudioBook>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_libraryManager.IsAudioFile(args.Path))
|
if (AudioFileParser.IsAudioFile(args.Path, _namingOptions))
|
||||||
{
|
{
|
||||||
var extension = Path.GetExtension(args.Path);
|
var extension = Path.GetExtension(args.Path);
|
||||||
|
|
||||||
@@ -107,7 +105,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
|||||||
var isMixedCollectionType = string.IsNullOrEmpty(collectionType);
|
var isMixedCollectionType = string.IsNullOrEmpty(collectionType);
|
||||||
|
|
||||||
// For conflicting extensions, give priority to videos
|
// For conflicting extensions, give priority to videos
|
||||||
if (isMixedCollectionType && _libraryManager.IsVideoFile(args.Path))
|
if (isMixedCollectionType && VideoResolver.IsVideoFile(args.Path, _namingOptions))
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -141,29 +139,23 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private T FindAudio<T>(ItemResolveArgs args, string path, Folder parent, List<FileSystemMetadata> fileSystemEntries, IDirectoryService directoryService, string collectionType, bool parseName)
|
private AudioBook FindAudioBook(ItemResolveArgs args, bool parseName)
|
||||||
where T : MediaBrowser.Controller.Entities.Audio.Audio, new()
|
|
||||||
{
|
{
|
||||||
// TODO: Allow GetMultiDiscMovie in here
|
// TODO: Allow GetMultiDiscMovie in here
|
||||||
const bool supportsMultiVersion = false;
|
var result = ResolveMultipleAudio(args.Parent, args.GetActualFileSystemChildren(), parseName);
|
||||||
|
|
||||||
var result = ResolveMultipleAudio<T>(parent, fileSystemEntries, directoryService, supportsMultiVersion, collectionType, parseName) ??
|
if (result == null || result.Items.Count != 1 || result.Items[0] is not AudioBook item)
|
||||||
new MultiItemResolverResult();
|
|
||||||
|
|
||||||
if (result.Items.Count == 1)
|
|
||||||
{
|
{
|
||||||
// If we were supporting this we'd be checking filesFromOtherItems
|
return null;
|
||||||
var item = (T)result.Items[0];
|
|
||||||
item.IsInMixedFolder = false;
|
|
||||||
item.Name = Path.GetFileName(item.ContainingFolderPath);
|
|
||||||
return item;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
// If we were supporting this we'd be checking filesFromOtherItems
|
||||||
|
item.IsInMixedFolder = false;
|
||||||
|
item.Name = Path.GetFileName(item.ContainingFolderPath);
|
||||||
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
private MultiItemResolverResult ResolveMultipleAudio<T>(Folder parent, IEnumerable<FileSystemMetadata> fileSystemEntries, IDirectoryService directoryService, bool suppportMultiEditions, string collectionType, bool parseName)
|
private MultiItemResolverResult ResolveMultipleAudio(Folder parent, IEnumerable<FileSystemMetadata> fileSystemEntries, bool parseName)
|
||||||
where T : MediaBrowser.Controller.Entities.Audio.Audio, new()
|
|
||||||
{
|
{
|
||||||
var files = new List<FileSystemMetadata>();
|
var files = new List<FileSystemMetadata>();
|
||||||
var items = new List<BaseItem>();
|
var items = new List<BaseItem>();
|
||||||
@@ -176,15 +168,13 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
|||||||
{
|
{
|
||||||
leftOver.Add(child);
|
leftOver.Add(child);
|
||||||
}
|
}
|
||||||
else if (!IsIgnored(child.Name))
|
else
|
||||||
{
|
{
|
||||||
files.Add(child);
|
files.Add(child);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var namingOptions = ((LibraryManager)_libraryManager).GetNamingOptions();
|
var resolver = new AudioBookListResolver(_namingOptions);
|
||||||
|
|
||||||
var resolver = new AudioBookListResolver(namingOptions);
|
|
||||||
var resolverResult = resolver.Resolve(files).ToList();
|
var resolverResult = resolver.Resolve(files).ToList();
|
||||||
|
|
||||||
var result = new MultiItemResolverResult
|
var result = new MultiItemResolverResult
|
||||||
@@ -210,7 +200,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
|||||||
|
|
||||||
var firstMedia = resolvedItem.Files[0];
|
var firstMedia = resolvedItem.Files[0];
|
||||||
|
|
||||||
var libraryItem = new T
|
var libraryItem = new AudioBook
|
||||||
{
|
{
|
||||||
Path = firstMedia.Path,
|
Path = firstMedia.Path,
|
||||||
IsInMixedFolder = isInMixedFolder,
|
IsInMixedFolder = isInMixedFolder,
|
||||||
@@ -230,12 +220,12 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool ContainsFile(List<AudioBookInfo> result, FileSystemMetadata file)
|
private static bool ContainsFile(IEnumerable<AudioBookInfo> result, FileSystemMetadata file)
|
||||||
{
|
{
|
||||||
return result.Any(i => ContainsFile(i, file));
|
return result.Any(i => ContainsFile(i, file));
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool ContainsFile(AudioBookInfo result, FileSystemMetadata file)
|
private static bool ContainsFile(AudioBookInfo result, FileSystemMetadata file)
|
||||||
{
|
{
|
||||||
return result.Files.Any(i => ContainsFile(i, file)) ||
|
return result.Files.Any(i => ContainsFile(i, file)) ||
|
||||||
result.AlternateVersions.Any(i => ContainsFile(i, file)) ||
|
result.AlternateVersions.Any(i => ContainsFile(i, file)) ||
|
||||||
@@ -246,10 +236,5 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
|||||||
{
|
{
|
||||||
return string.Equals(result.Path, file.FullName, StringComparison.OrdinalIgnoreCase);
|
return string.Equals(result.Path, file.FullName, StringComparison.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsIgnored(string filename)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ using System.Linq;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Emby.Naming.Audio;
|
using Emby.Naming.Audio;
|
||||||
|
using Emby.Naming.Common;
|
||||||
using MediaBrowser.Controller.Entities.Audio;
|
using MediaBrowser.Controller.Entities.Audio;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.Providers;
|
using MediaBrowser.Controller.Providers;
|
||||||
@@ -22,20 +23,17 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
|||||||
public class MusicAlbumResolver : ItemResolver<MusicAlbum>
|
public class MusicAlbumResolver : ItemResolver<MusicAlbum>
|
||||||
{
|
{
|
||||||
private readonly ILogger<MusicAlbumResolver> _logger;
|
private readonly ILogger<MusicAlbumResolver> _logger;
|
||||||
private readonly IFileSystem _fileSystem;
|
private readonly NamingOptions _namingOptions;
|
||||||
private readonly ILibraryManager _libraryManager;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="MusicAlbumResolver"/> class.
|
/// Initializes a new instance of the <see cref="MusicAlbumResolver"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="logger">The logger.</param>
|
/// <param name="logger">The logger.</param>
|
||||||
/// <param name="fileSystem">The file system.</param>
|
/// <param name="namingOptions">The naming options.</param>
|
||||||
/// <param name="libraryManager">The library manager.</param>
|
public MusicAlbumResolver(ILogger<MusicAlbumResolver> logger, NamingOptions namingOptions)
|
||||||
public MusicAlbumResolver(ILogger<MusicAlbumResolver> logger, IFileSystem fileSystem, ILibraryManager libraryManager)
|
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_fileSystem = fileSystem;
|
_namingOptions = namingOptions;
|
||||||
_libraryManager = libraryManager;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -87,7 +85,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
|||||||
/// <returns><c>true</c> if the provided path points to a music album, <c>false</c> otherwise.</returns>
|
/// <returns><c>true</c> if the provided path points to a music album, <c>false</c> otherwise.</returns>
|
||||||
public bool IsMusicAlbum(string path, IDirectoryService directoryService)
|
public bool IsMusicAlbum(string path, IDirectoryService directoryService)
|
||||||
{
|
{
|
||||||
return ContainsMusic(directoryService.GetFileSystemEntries(path), true, directoryService, _logger, _fileSystem, _libraryManager);
|
return ContainsMusic(directoryService.GetFileSystemEntries(path), true, directoryService);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -101,7 +99,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
|||||||
if (args.IsDirectory)
|
if (args.IsDirectory)
|
||||||
{
|
{
|
||||||
// if (args.Parent is MusicArtist) return true; // saves us from testing children twice
|
// if (args.Parent is MusicArtist) return true; // saves us from testing children twice
|
||||||
if (ContainsMusic(args.FileSystemChildren, true, args.DirectoryService, _logger, _fileSystem, _libraryManager))
|
if (ContainsMusic(args.FileSystemChildren, true, args.DirectoryService))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -114,15 +112,12 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
|||||||
/// Determine if the supplied list contains what we should consider music.
|
/// Determine if the supplied list contains what we should consider music.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private bool ContainsMusic(
|
private bool ContainsMusic(
|
||||||
IEnumerable<FileSystemMetadata> list,
|
ICollection<FileSystemMetadata> list,
|
||||||
bool allowSubfolders,
|
bool allowSubfolders,
|
||||||
IDirectoryService directoryService,
|
IDirectoryService directoryService)
|
||||||
ILogger<MusicAlbumResolver> logger,
|
|
||||||
IFileSystem fileSystem,
|
|
||||||
ILibraryManager libraryManager)
|
|
||||||
{
|
{
|
||||||
// check for audio files before digging down into directories
|
// check for audio files before digging down into directories
|
||||||
var foundAudioFile = list.Any(fileSystemInfo => !fileSystemInfo.IsDirectory && libraryManager.IsAudioFile(fileSystemInfo.FullName));
|
var foundAudioFile = list.Any(fileSystemInfo => !fileSystemInfo.IsDirectory && AudioFileParser.IsAudioFile(fileSystemInfo.FullName, _namingOptions));
|
||||||
if (foundAudioFile)
|
if (foundAudioFile)
|
||||||
{
|
{
|
||||||
// at least one audio file exists
|
// at least one audio file exists
|
||||||
@@ -137,21 +132,20 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
|||||||
|
|
||||||
var discSubfolderCount = 0;
|
var discSubfolderCount = 0;
|
||||||
|
|
||||||
var namingOptions = ((LibraryManager)_libraryManager).GetNamingOptions();
|
var parser = new AlbumParser(_namingOptions);
|
||||||
var parser = new AlbumParser(namingOptions);
|
|
||||||
|
|
||||||
var directories = list.Where(fileSystemInfo => fileSystemInfo.IsDirectory);
|
var directories = list.Where(fileSystemInfo => fileSystemInfo.IsDirectory);
|
||||||
|
|
||||||
var result = Parallel.ForEach(directories, (fileSystemInfo, state) =>
|
var result = Parallel.ForEach(directories, (fileSystemInfo, state) =>
|
||||||
{
|
{
|
||||||
var path = fileSystemInfo.FullName;
|
var path = fileSystemInfo.FullName;
|
||||||
var hasMusic = ContainsMusic(directoryService.GetFileSystemEntries(path), false, directoryService, logger, fileSystem, libraryManager);
|
var hasMusic = ContainsMusic(directoryService.GetFileSystemEntries(path), false, directoryService);
|
||||||
|
|
||||||
if (hasMusic)
|
if (hasMusic)
|
||||||
{
|
{
|
||||||
if (parser.IsMultiPart(path))
|
if (parser.IsMultiPart(path))
|
||||||
{
|
{
|
||||||
logger.LogDebug("Found multi-disc folder: {Path}", path);
|
_logger.LogDebug("Found multi-disc folder: {Path}", path);
|
||||||
Interlocked.Increment(ref discSubfolderCount);
|
Interlocked.Increment(ref discSubfolderCount);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user