mirror of
https://github.com/jellyfin/jellyfin.git
synced 2025-12-12 20:13:01 +03:00
Compare commits
869 Commits
v10.8.0-al
...
v10.8.9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0ac18a50f5 | ||
|
|
4ebae248df | ||
|
|
fbb9acf58b | ||
|
|
87f081c8ac | ||
|
|
44077b4f5c | ||
|
|
060a80bef7 | ||
|
|
885a1b02c1 | ||
|
|
1dea309ae4 | ||
|
|
32227c76b7 | ||
|
|
a7c43643a4 | ||
|
|
2a5efeb3bb | ||
|
|
464136cfc9 | ||
|
|
31673cc27d | ||
|
|
6a909f956e | ||
|
|
20e9db8308 | ||
|
|
f8b8fdace6 | ||
|
|
2a6e292153 | ||
|
|
c9f3d9bdde | ||
|
|
8d49e0099c | ||
|
|
76e3da6a40 | ||
|
|
f0faddcc44 | ||
|
|
e6606d41ce | ||
|
|
7c8aea7859 | ||
|
|
c4c5af40a1 | ||
|
|
383d514353 | ||
|
|
6fc8237242 | ||
|
|
79d7a4d4df | ||
|
|
e90031b4cc | ||
|
|
4f3d562d75 | ||
|
|
6c8b40f413 | ||
|
|
ec81dc9be2 | ||
|
|
45f3fb1cfc | ||
|
|
f83a24ec43 | ||
|
|
84c03a2d93 | ||
|
|
bc8e249080 | ||
|
|
5ea9a74289 | ||
|
|
987d31ea16 | ||
|
|
f850779781 | ||
|
|
3bdc2bff5f | ||
|
|
5c6a84549a | ||
|
|
48da35f91f | ||
|
|
39b29eb9f1 | ||
|
|
a6740bf51e | ||
|
|
c7797d3ead | ||
|
|
c86d5838be | ||
|
|
43223b9036 | ||
|
|
c71385d2db | ||
|
|
ad5becc524 | ||
|
|
7c75dcfb9c | ||
|
|
7937e31a9b | ||
|
|
1f1f26306b | ||
|
|
577399ca05 | ||
|
|
e7ea7c0383 | ||
|
|
9fe7751d05 | ||
|
|
bf129ab9b8 | ||
|
|
6d23de64c0 | ||
|
|
e4f48bb486 | ||
|
|
866b4460b1 | ||
|
|
9db0b275ff | ||
|
|
14008fd7d0 | ||
|
|
8532d88a71 | ||
|
|
737c739d33 | ||
|
|
679e83082f | ||
|
|
d8e53f35a5 | ||
|
|
7a0e7b3cf8 | ||
|
|
373c63bcc7 | ||
|
|
be5d343efb | ||
|
|
c9c91cc34e | ||
|
|
774b4a0d3f | ||
|
|
a26cded0f5 | ||
|
|
4ec82ec662 | ||
|
|
879787212e | ||
|
|
23100c9b86 | ||
|
|
e6124bc154 | ||
|
|
8753b7200f | ||
|
|
88d5230bab | ||
|
|
2920c52d61 | ||
|
|
de196a7687 | ||
|
|
ba026716c1 | ||
|
|
125ee88311 | ||
|
|
c53f6a2890 | ||
|
|
649b4c49e0 | ||
|
|
848ea703bc | ||
|
|
0adadff3e7 | ||
|
|
ffdc3a6734 | ||
|
|
a51cd4f8db | ||
|
|
af87706379 | ||
|
|
8422ab687b | ||
|
|
64753cfc7f | ||
|
|
632fb05f46 | ||
|
|
527ed0607d | ||
|
|
b59daab273 | ||
|
|
8f28d52929 | ||
|
|
749b263c48 | ||
|
|
80c68b8948 | ||
|
|
a5687793c9 | ||
|
|
b344771f8a | ||
|
|
3ff78b687d | ||
|
|
d260f30810 | ||
|
|
7ffdde9a0b | ||
|
|
e14194bfe2 | ||
|
|
3bf1a7e445 | ||
|
|
1faee43b11 | ||
|
|
31f9938e3a | ||
|
|
ae9fd4ab35 | ||
|
|
71ed7f7676 | ||
|
|
3b6e003029 | ||
|
|
9357d610b1 | ||
|
|
1d4755894e | ||
|
|
2320f06666 | ||
|
|
8296f07a39 | ||
|
|
30f6263806 | ||
|
|
a9249393e1 | ||
|
|
f49a051a5f | ||
|
|
5bcab0f0f8 | ||
|
|
c5a2ff8ac4 | ||
|
|
494ed7e4d2 | ||
|
|
dd97e6bc45 | ||
|
|
7323ccfc23 | ||
|
|
d258a87fda | ||
|
|
77a007a24d | ||
|
|
a380153f92 | ||
|
|
56c81696d3 | ||
|
|
7297431f23 | ||
|
|
f2c7bccb89 | ||
|
|
b0b4068ddf | ||
|
|
3bd2cc9860 | ||
|
|
feb035b9e0 | ||
|
|
82f362abd9 | ||
|
|
04b73cace6 | ||
|
|
3b69f38a1f | ||
|
|
126da94020 | ||
|
|
f9dffa767f | ||
|
|
444b0ea310 | ||
|
|
484427b4aa | ||
|
|
c3f0649fde | ||
|
|
e877486056 | ||
|
|
9854751137 | ||
|
|
057e8ef240 | ||
|
|
205783f46f | ||
|
|
b2fb96ffed | ||
|
|
ee22feb89a | ||
|
|
ca5979cd77 | ||
|
|
d36f49589a | ||
|
|
70f37f0527 | ||
|
|
dfe0aef530 | ||
|
|
9e31d5a73f | ||
|
|
f088ca5555 | ||
|
|
2b46917dcf | ||
|
|
7bae6eff95 | ||
|
|
d0fd23bb4b | ||
|
|
d694a6c09a | ||
|
|
58f61ed118 | ||
|
|
b9da0e7f83 | ||
|
|
7eaa0600e0 | ||
|
|
47c2c536e4 | ||
|
|
7ef9e95d75 | ||
|
|
f8ea4577ab | ||
|
|
72da42cb0a | ||
|
|
dbfa0f3027 | ||
|
|
78f437401b | ||
|
|
1db748399c | ||
|
|
a41c67d16b | ||
|
|
84a1674f39 | ||
|
|
81e535fc62 | ||
|
|
f9d26ea1bc | ||
|
|
5f3dbd8294 | ||
|
|
9cebdfdec0 | ||
|
|
891ccd7bb2 | ||
|
|
54778d875d | ||
|
|
39d185c7b1 | ||
|
|
a7d45b5d3a | ||
|
|
7efa4e38c1 | ||
|
|
506ed6940b | ||
|
|
5dbe16d3e6 | ||
|
|
4c178e9188 | ||
|
|
50bc41d84d | ||
|
|
e931f5a32b | ||
|
|
d2caed25fb | ||
|
|
3f37ef70e1 | ||
|
|
d342b79218 | ||
|
|
c35fc382d4 | ||
|
|
cb6e6879e2 | ||
|
|
a71b190142 | ||
|
|
910df89cce | ||
|
|
5f15339919 | ||
|
|
56e7b323de | ||
|
|
a3a751a4f5 | ||
|
|
c85255a615 | ||
|
|
8ea8dcf128 | ||
|
|
7884e7e829 | ||
|
|
3478554249 | ||
|
|
9898c10880 | ||
|
|
ec2ad4ec8c | ||
|
|
1ffc77b43d | ||
|
|
ae79bbc34c | ||
|
|
52704e8dd0 | ||
|
|
294ab0757e | ||
|
|
bdd52df230 | ||
|
|
73117b079c | ||
|
|
b60905f991 | ||
|
|
6d5c697183 | ||
|
|
24c56328f2 | ||
|
|
ef037ad371 | ||
|
|
a64e21f57a | ||
|
|
56e135f5e6 | ||
|
|
ae22d0b7a5 | ||
|
|
b36543275f | ||
|
|
2c0c3eb3ee | ||
|
|
f1d56aa5ce | ||
|
|
0b6fbebf72 | ||
|
|
db714f967e | ||
|
|
f020bd6f3b | ||
|
|
3275f83c3b | ||
|
|
f7813803c2 | ||
|
|
477b922e4a | ||
|
|
be72001ff9 | ||
|
|
6749313249 | ||
|
|
4ebe70cf6a | ||
|
|
c4051ac16d | ||
|
|
3491f0968b | ||
|
|
fd4ffc6ba3 | ||
|
|
5912a49d1d | ||
|
|
f336647d57 | ||
|
|
9b805c9e83 | ||
|
|
19ccf414ac | ||
|
|
0504ed9fe6 | ||
|
|
c243f588a0 | ||
|
|
46491d0813 | ||
|
|
c7c0cdad95 | ||
|
|
255f5a6707 | ||
|
|
3f497459cb | ||
|
|
5d66c84f2d | ||
|
|
052a59ac3e | ||
|
|
1b8a251991 | ||
|
|
1a787e273a | ||
|
|
16fba6035c | ||
|
|
d73e9f3af5 | ||
|
|
2888080098 | ||
|
|
c8282e8441 | ||
|
|
b295b0478c | ||
|
|
42aaea3556 | ||
|
|
07b39655eb | ||
|
|
0f75f17736 | ||
|
|
83d8dbf93e | ||
|
|
e5aa708cb9 | ||
|
|
ac760b9c86 | ||
|
|
c07c7b753c | ||
|
|
8b69b0f521 | ||
|
|
079fac4a54 | ||
|
|
21afec3225 | ||
|
|
11c5a0b182 | ||
|
|
f318417c4f | ||
|
|
874fcaba69 | ||
|
|
5204863705 | ||
|
|
93941f9728 | ||
|
|
aa0f6cb5eb | ||
|
|
69cc1e0bd8 | ||
|
|
007856e61a | ||
|
|
b4954985be | ||
|
|
6b16d90b9b | ||
|
|
0f7ba42987 | ||
|
|
2a89683e80 | ||
|
|
8595a979a8 | ||
|
|
1900096012 | ||
|
|
0e8da3e805 | ||
|
|
84c9e7a22b | ||
|
|
be28f940b7 | ||
|
|
fb95fb1a73 | ||
|
|
910995f922 | ||
|
|
4f0666ac5c | ||
|
|
4bfadbc636 | ||
|
|
2cc896251f | ||
|
|
5e343d30e1 | ||
|
|
df6c5b6d42 | ||
|
|
754bda8f73 | ||
|
|
3721b5e985 | ||
|
|
3e8fe1ce11 | ||
|
|
77c73e241f | ||
|
|
9954cbd550 | ||
|
|
d8f1a87c85 | ||
|
|
bf0a7c374c | ||
|
|
8a6b26cd42 | ||
|
|
b369194710 | ||
|
|
293bcfb342 | ||
|
|
1c5571b24e | ||
|
|
b507d1a780 | ||
|
|
d471be8d92 | ||
|
|
3c5b4b9a27 | ||
|
|
c9491cf317 | ||
|
|
c5dae18034 | ||
|
|
ff4f624850 | ||
|
|
d29a423475 | ||
|
|
4c0510ee6d | ||
|
|
492c6bbd7e | ||
|
|
84878f537c | ||
|
|
825e6460c9 | ||
|
|
760b021032 | ||
|
|
a532a866e3 | ||
|
|
71bf567045 | ||
|
|
044ff0542b | ||
|
|
abfbd04782 | ||
|
|
8d0024ec49 | ||
|
|
8f761a64f5 | ||
|
|
a64eebe79f | ||
|
|
a82e378da9 | ||
|
|
884a59da07 | ||
|
|
5a9e5e0d5d | ||
|
|
85cfea4c50 | ||
|
|
3ea67374ae | ||
|
|
5da4bcc782 | ||
|
|
b46d61dfdf | ||
|
|
8119e4a573 | ||
|
|
de3c68d474 | ||
|
|
7aa0db24d8 | ||
|
|
be832e82cc | ||
|
|
368d10d042 | ||
|
|
9523a1682b | ||
|
|
8bb4cd017c | ||
|
|
c5f09ab650 | ||
|
|
99df9c8fcd | ||
|
|
4b563f4d7e | ||
|
|
e67d8ce077 | ||
|
|
39196bb5e2 | ||
|
|
5386f06095 | ||
|
|
5a9afb0874 | ||
|
|
029be321d1 | ||
|
|
96b7c46df4 | ||
|
|
f7ef7d9eda | ||
|
|
c69e79f64d | ||
|
|
4b1256e67b | ||
|
|
8d1d973438 | ||
|
|
60affd0965 | ||
|
|
8a1eca0913 | ||
|
|
a4e4b761d5 | ||
|
|
2be9a34b26 | ||
|
|
0c8b9091a5 | ||
|
|
128d54622a | ||
|
|
21ce0e58c6 | ||
|
|
3229ba4918 | ||
|
|
105f057512 | ||
|
|
b6a2640f22 | ||
|
|
a2abae3014 | ||
|
|
7084541508 | ||
|
|
e87240b374 | ||
|
|
057d5dfc25 | ||
|
|
12f9132975 | ||
|
|
4a1aa619d2 | ||
|
|
1002d1d193 | ||
|
|
7c91543694 | ||
|
|
b04211a707 | ||
|
|
fcb65ac38d | ||
|
|
727402f7f7 | ||
|
|
2a1ca4badc | ||
|
|
7f52f77ef5 | ||
|
|
1d5961126e | ||
|
|
ec6f7bdcff | ||
|
|
65c47c79da | ||
|
|
5d9df10e27 | ||
|
|
d45d228b36 | ||
|
|
2b7d139b5b | ||
|
|
a280ff603f | ||
|
|
9beb3aff4e | ||
|
|
066bdc1e72 | ||
|
|
5833c70725 | ||
|
|
cd93f49fa8 | ||
|
|
93009682b3 | ||
|
|
56573f14b0 | ||
|
|
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 | ||
|
|
360fd70fc7 | ||
|
|
b17fe35e2e | ||
|
|
9e0958d822 | ||
|
|
9e23af5636 | ||
|
|
6ad020acb2 | ||
|
|
c70452b4a4 | ||
|
|
045ef4b726 | ||
|
|
68db3be0e7 | ||
|
|
e026ba84c5 | ||
|
|
3fb3ee074a | ||
|
|
0fd4ff4451 | ||
|
|
c934269a6e | ||
|
|
4ba168c8a1 | ||
|
|
dc222b75c5 | ||
|
|
1dfbeae045 | ||
|
|
19164378f2 | ||
|
|
c81d2e9dec | ||
|
|
ce66df2c92 | ||
|
|
29755c9384 | ||
|
|
608a91162a | ||
|
|
853ef727da | ||
|
|
76e640b0b9 | ||
|
|
e3a7c9238d | ||
|
|
cecfdeeec3 | ||
|
|
fcf5b9b46e | ||
|
|
9a2b88cb1f | ||
|
|
2899b77cd5 | ||
|
|
41383e6fe4 | ||
|
|
64a13a5d42 | ||
|
|
43ea4af30f | ||
|
|
30f3be1da0 | ||
|
|
3e5cb8e04e | ||
|
|
c7b25a9fe4 | ||
|
|
fa38b741ce | ||
|
|
be233b49b6 | ||
|
|
35c0801d6c | ||
|
|
ee8bd9b3b5 | ||
|
|
30230aff73 | ||
|
|
d995f0e092 | ||
|
|
6e77d50563 | ||
|
|
3531dc85ae | ||
|
|
9c15f96e12 | ||
|
|
86a5e72a65 |
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:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v1
|
||||
uses: actions/setup-dotnet@v2
|
||||
with:
|
||||
dotnet-version: '6.0.x'
|
||||
|
||||
|
||||
4
.github/workflows/commands.yml
vendored
4
.github/workflows/commands.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
||||
reactions: '+1'
|
||||
|
||||
- name: Checkout the latest code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
token: ${{ secrets.JF_BOT_TOKEN }}
|
||||
fetch-depth: 0
|
||||
@@ -47,7 +47,7 @@ jobs:
|
||||
reactions: eyes
|
||||
|
||||
- name: Checkout the latest code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
token: ${{ secrets.JF_BOT_TOKEN }}
|
||||
fetch-depth: 0
|
||||
|
||||
10
.github/workflows/openapi.yml
vendored
10
.github/workflows/openapi.yml
vendored
@@ -12,12 +12,12 @@ jobs:
|
||||
permissions: read-all
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.ref }}
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
||||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v1
|
||||
uses: actions/setup-dotnet@v2
|
||||
with:
|
||||
dotnet-version: '6.0.x'
|
||||
- name: Generate openapi.json
|
||||
@@ -37,11 +37,11 @@ jobs:
|
||||
permissions: read-all
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ github.base_ref }}
|
||||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v1
|
||||
uses: actions/setup-dotnet@v2
|
||||
with:
|
||||
dotnet-version: '6.0.x'
|
||||
- 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
|
||||
|
||||
- [1337joe](https://github.com/1337joe)
|
||||
- [97carmine](https://github.com/97carmine)
|
||||
- [Abbe98](https://github.com/Abbe98)
|
||||
- [agrenott](https://github.com/agrenott)
|
||||
@@ -26,6 +27,7 @@
|
||||
- [cvium](https://github.com/cvium)
|
||||
- [dannymichel](https://github.com/dannymichel)
|
||||
- [DaveChild](https://github.com/DaveChild)
|
||||
- [DavidFair](https://github.com/DavidFair)
|
||||
- [Delgan](https://github.com/Delgan)
|
||||
- [dcrdev](https://github.com/dcrdev)
|
||||
- [dhartung](https://github.com/dhartung)
|
||||
@@ -35,6 +37,7 @@
|
||||
- [dmitrylyzo](https://github.com/dmitrylyzo)
|
||||
- [DMouse10462](https://github.com/DMouse10462)
|
||||
- [DrPandemic](https://github.com/DrPandemic)
|
||||
- [eglia](https://github.com/eglia)
|
||||
- [EraYaN](https://github.com/EraYaN)
|
||||
- [escabe](https://github.com/escabe)
|
||||
- [excelite](https://github.com/excelite)
|
||||
@@ -45,6 +48,7 @@
|
||||
- [Froghut](https://github.com/Froghut)
|
||||
- [fruhnow](https://github.com/fruhnow)
|
||||
- [geilername](https://github.com/geilername)
|
||||
- [GermanCoding](https://github.com/GermanCoding)
|
||||
- [gnattu](https://github.com/gnattu)
|
||||
- [GodTamIt](https://github.com/GodTamIt)
|
||||
- [grafixeyehero](https://github.com/grafixeyehero)
|
||||
@@ -76,6 +80,7 @@
|
||||
- [mitchfizz05](https://github.com/mitchfizz05)
|
||||
- [MrTimscampi](https://github.com/MrTimscampi)
|
||||
- [n8225](https://github.com/n8225)
|
||||
- [Nalsai](https://github.com/Nalsai)
|
||||
- [Narfinger](https://github.com/Narfinger)
|
||||
- [NathanPickard](https://github.com/NathanPickard)
|
||||
- [neilsb](https://github.com/neilsb)
|
||||
@@ -115,6 +120,7 @@
|
||||
- [ssenart](https://github.com/ssenart)
|
||||
- [stanionascu](https://github.com/stanionascu)
|
||||
- [stevehayles](https://github.com/stevehayles)
|
||||
- [StollD](https://github.com/StollD)
|
||||
- [SuperSandro2000](https://github.com/SuperSandro2000)
|
||||
- [tbraeutigam](https://github.com/tbraeutigam)
|
||||
- [teacupx](https://github.com/teacupx)
|
||||
@@ -151,6 +157,9 @@
|
||||
- [peterspenler](https://github.com/peterspenler)
|
||||
- [MBR-0001](https://github.com/MBR-0001)
|
||||
- [jonas-resch](https://github.com/jonas-resch)
|
||||
- [vgambier](https://github.com/vgambier)
|
||||
- [MinecraftPlaye](https://github.com/MinecraftPlaye)
|
||||
- [RealGreenDragon](https://github.com/RealGreenDragon)
|
||||
|
||||
# Emby Contributors
|
||||
|
||||
@@ -217,3 +226,5 @@
|
||||
- [olsh](https://github.com/olsh)
|
||||
- [lbenini](https://github.com/lbenini)
|
||||
- [gnuyent](https://github.com/gnuyent)
|
||||
- [Matthew Jones](https://github.com/matthew-jones-uk)
|
||||
- [Jakob Kukla](https://github.com/jakobkukla)
|
||||
|
||||
@@ -14,4 +14,8 @@
|
||||
<AnalysisMode>AllEnabledByDefault</AnalysisMode>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AdditionalFiles Include="$(SolutionDir)/BannedSymbols.txt" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
15
Dockerfile
15
Dockerfile
@@ -22,10 +22,10 @@ ARG APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=DontWarn
|
||||
ENV NVIDIA_DRIVER_CAPABILITIES="compute,video,utility"
|
||||
|
||||
# https://github.com/intel/compute-runtime/releases
|
||||
ARG GMMLIB_VERSION=21.2.1
|
||||
ARG IGC_VERSION=1.0.8517
|
||||
ARG NEO_VERSION=21.35.20826
|
||||
ARG LEVEL_ZERO_VERSION=1.2.20826
|
||||
ARG GMMLIB_VERSION=22.0.2
|
||||
ARG IGC_VERSION=1.0.10395
|
||||
ARG NEO_VERSION=22.08.22549
|
||||
ARG LEVEL_ZERO_VERSION=1.3.22549
|
||||
|
||||
# Install dependencies:
|
||||
# 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/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/compute-runtime/releases/download/${NEO_VERSION}/intel-opencl_${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-opencl-icd_${NEO_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 \
|
||||
&& cd .. \
|
||||
@@ -73,7 +72,7 @@ COPY . .
|
||||
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||
# because of changes in docker and systemd we need to not build in parallel at the moment
|
||||
# see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
|
||||
RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="/jellyfin" --self-contained --runtime linux-x64 "-p:DebugSymbols=false;DebugType=none"
|
||||
RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="/jellyfin" --self-contained --runtime linux-x64 -p:DebugSymbols=false -p:DebugType=none
|
||||
|
||||
FROM app
|
||||
|
||||
@@ -83,7 +82,7 @@ COPY --from=builder /jellyfin /jellyfin
|
||||
COPY --from=web-builder /dist /jellyfin/jellyfin-web
|
||||
|
||||
EXPOSE 8096
|
||||
VOLUME /cache /config /media
|
||||
VOLUME /cache /config
|
||||
ENTRYPOINT ["./jellyfin/jellyfin", \
|
||||
"--datadir", "/config", \
|
||||
"--cachedir", "/cache", \
|
||||
|
||||
@@ -64,7 +64,7 @@ ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||
# Discard objs - may cause failures if exists
|
||||
RUN find . -type d -name obj | xargs -r rm -r
|
||||
# Build
|
||||
RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm "-p:DebugSymbols=false;DebugType=none"
|
||||
RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm -p:DebugSymbols=false -p:DebugType=none
|
||||
|
||||
FROM app
|
||||
|
||||
@@ -74,7 +74,7 @@ COPY --from=builder /jellyfin /jellyfin
|
||||
COPY --from=web-builder /dist /jellyfin/jellyfin-web
|
||||
|
||||
EXPOSE 8096
|
||||
VOLUME /cache /config /media
|
||||
VOLUME /cache /config
|
||||
ENTRYPOINT ["./jellyfin/jellyfin", \
|
||||
"--datadir", "/config", \
|
||||
"--cachedir", "/cache", \
|
||||
|
||||
@@ -55,7 +55,7 @@ ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||
# Discard objs - may cause failures if exists
|
||||
RUN find . -type d -name obj | xargs -r rm -r
|
||||
# Build
|
||||
RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm64 "-p:DebugSymbols=false;DebugType=none"
|
||||
RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm64 -p:DebugSymbols=false -p:DebugType=none
|
||||
|
||||
FROM app
|
||||
|
||||
@@ -65,7 +65,7 @@ COPY --from=builder /jellyfin /jellyfin
|
||||
COPY --from=web-builder /dist /jellyfin/jellyfin-web
|
||||
|
||||
EXPOSE 8096
|
||||
VOLUME /cache /config /media
|
||||
VOLUME /cache /config
|
||||
ENTRYPOINT ["./jellyfin/jellyfin", \
|
||||
"--datadir", "/config", \
|
||||
"--cachedir", "/cache", \
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Emby.Dlna.Configuration
|
||||
public DlnaOptions()
|
||||
{
|
||||
EnablePlayTo = true;
|
||||
EnableServer = true;
|
||||
EnableServer = false;
|
||||
BlastAliveMessages = true;
|
||||
SendOnlyMatchedHost = true;
|
||||
ClientDiscoveryIntervalSeconds = 60;
|
||||
|
||||
@@ -619,7 +619,7 @@ namespace Emby.Dlna.ContentDirectory
|
||||
|
||||
var queryResult = folder.GetItems(query);
|
||||
|
||||
return ToResult(queryResult);
|
||||
return ToResult(startIndex, queryResult);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -642,7 +642,7 @@ namespace Emby.Dlna.ContentDirectory
|
||||
|
||||
var result = _libraryManager.GetItemsResult(query);
|
||||
|
||||
return ToResult(result);
|
||||
return ToResult(startIndex, result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -707,11 +707,10 @@ namespace Emby.Dlna.ContentDirectory
|
||||
serverItems = serverItems[..limit.Value];
|
||||
}
|
||||
|
||||
return new QueryResult<ServerItem>
|
||||
{
|
||||
Items = serverItems,
|
||||
TotalRecordCount = serverItems.Length
|
||||
};
|
||||
return new QueryResult<ServerItem>(
|
||||
startIndex,
|
||||
serverItems.Length,
|
||||
serverItems);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -764,11 +763,10 @@ namespace Emby.Dlna.ContentDirectory
|
||||
array = array[..limit.Value];
|
||||
}
|
||||
|
||||
return new QueryResult<ServerItem>
|
||||
{
|
||||
Items = array,
|
||||
TotalRecordCount = array.Length
|
||||
};
|
||||
return new QueryResult<ServerItem>(
|
||||
startIndex,
|
||||
array.Length,
|
||||
array);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -790,11 +788,10 @@ namespace Emby.Dlna.ContentDirectory
|
||||
.Select(i => new ServerItem(i, StubType.Folder))
|
||||
.ToArray();
|
||||
|
||||
return new QueryResult<ServerItem>
|
||||
{
|
||||
Items = items,
|
||||
TotalRecordCount = totalRecordCount
|
||||
};
|
||||
return new QueryResult<ServerItem>(
|
||||
startIndex,
|
||||
totalRecordCount,
|
||||
items);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -850,11 +847,10 @@ namespace Emby.Dlna.ContentDirectory
|
||||
serverItems = serverItems[..limit.Value];
|
||||
}
|
||||
|
||||
return new QueryResult<ServerItem>
|
||||
{
|
||||
Items = serverItems,
|
||||
TotalRecordCount = serverItems.Length
|
||||
};
|
||||
return new QueryResult<ServerItem>(
|
||||
startIndex,
|
||||
serverItems.Length,
|
||||
serverItems);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -879,7 +875,7 @@ namespace Emby.Dlna.ContentDirectory
|
||||
|
||||
var result = _libraryManager.GetItemsResult(query);
|
||||
|
||||
return ToResult(result);
|
||||
return ToResult(query.StartIndex, result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -894,7 +890,7 @@ namespace Emby.Dlna.ContentDirectory
|
||||
|
||||
var result = _libraryManager.GetItemsResult(query);
|
||||
|
||||
return ToResult(result);
|
||||
return ToResult(query.StartIndex, result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -914,7 +910,7 @@ namespace Emby.Dlna.ContentDirectory
|
||||
|
||||
var result = _libraryManager.GetItemsResult(query);
|
||||
|
||||
return ToResult(result);
|
||||
return ToResult(query.StartIndex, result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -931,7 +927,7 @@ namespace Emby.Dlna.ContentDirectory
|
||||
query.AncestorIds = new[] { parent.Id };
|
||||
var genresResult = _libraryManager.GetGenres(query);
|
||||
|
||||
return ToResult(genresResult);
|
||||
return ToResult(query.StartIndex, genresResult);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -947,7 +943,7 @@ namespace Emby.Dlna.ContentDirectory
|
||||
query.AncestorIds = new[] { parent.Id };
|
||||
var genresResult = _libraryManager.GetMusicGenres(query);
|
||||
|
||||
return ToResult(genresResult);
|
||||
return ToResult(query.StartIndex, genresResult);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -963,7 +959,7 @@ namespace Emby.Dlna.ContentDirectory
|
||||
query.AncestorIds = new[] { parent.Id };
|
||||
var artists = _libraryManager.GetAlbumArtists(query);
|
||||
|
||||
return ToResult(artists);
|
||||
return ToResult(query.StartIndex, artists);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -978,7 +974,7 @@ namespace Emby.Dlna.ContentDirectory
|
||||
query.OrderBy = Array.Empty<(string, SortOrder)>();
|
||||
query.AncestorIds = new[] { parent.Id };
|
||||
var artists = _libraryManager.GetArtists(query);
|
||||
return ToResult(artists);
|
||||
return ToResult(query.StartIndex, artists);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -994,7 +990,7 @@ namespace Emby.Dlna.ContentDirectory
|
||||
query.AncestorIds = new[] { parent.Id };
|
||||
query.IsFavorite = true;
|
||||
var artists = _libraryManager.GetArtists(query);
|
||||
return ToResult(artists);
|
||||
return ToResult(query.StartIndex, artists);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1010,7 +1006,7 @@ namespace Emby.Dlna.ContentDirectory
|
||||
|
||||
var result = _libraryManager.GetItemsResult(query);
|
||||
|
||||
return ToResult(result);
|
||||
return ToResult(query.StartIndex, result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1034,7 +1030,7 @@ namespace Emby.Dlna.ContentDirectory
|
||||
new[] { parent },
|
||||
query.DtoOptions);
|
||||
|
||||
return ToResult(result);
|
||||
return ToResult(query.StartIndex, result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1060,7 +1056,7 @@ namespace Emby.Dlna.ContentDirectory
|
||||
},
|
||||
query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).Where(i => i != null).ToArray();
|
||||
|
||||
return ToResult(items);
|
||||
return ToResult(query.StartIndex, items);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1087,7 +1083,7 @@ namespace Emby.Dlna.ContentDirectory
|
||||
|
||||
var result = _libraryManager.GetItemsResult(query);
|
||||
|
||||
return ToResult(result);
|
||||
return ToResult(startIndex, result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1118,7 +1114,7 @@ namespace Emby.Dlna.ContentDirectory
|
||||
|
||||
var result = _libraryManager.GetItemsResult(query);
|
||||
|
||||
return ToResult(result);
|
||||
return ToResult(startIndex, result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1145,33 +1141,34 @@ namespace Emby.Dlna.ContentDirectory
|
||||
|
||||
var result = _libraryManager.GetItemsResult(query);
|
||||
|
||||
return ToResult(result);
|
||||
return ToResult(startIndex, result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts <see cref="IReadOnlyCollection{BaseItem}"/> into a <see cref="QueryResult{ServerItem}"/>.
|
||||
/// </summary>
|
||||
/// <param name="startIndex">The start index.</param>
|
||||
/// <param name="result">An array of <see cref="BaseItem"/>.</param>
|
||||
/// <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
|
||||
.Select(i => new ServerItem(i, null))
|
||||
.ToArray();
|
||||
|
||||
return new QueryResult<ServerItem>
|
||||
{
|
||||
TotalRecordCount = result.Count,
|
||||
Items = serverItems
|
||||
};
|
||||
return new QueryResult<ServerItem>(
|
||||
startIndex,
|
||||
result.Count,
|
||||
serverItems);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a <see cref="QueryResult{BaseItem}"/> to a <see cref="QueryResult{ServerItem}"/>.
|
||||
/// </summary>
|
||||
/// <param name="startIndex">The index the result started at.</param>
|
||||
/// <param name="result">A <see cref="QueryResult{BaseItem}"/>.</param>
|
||||
/// <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 serverItems = new ServerItem[length];
|
||||
@@ -1180,32 +1177,31 @@ namespace Emby.Dlna.ContentDirectory
|
||||
serverItems[i] = new ServerItem(result.Items[i], null);
|
||||
}
|
||||
|
||||
return new QueryResult<ServerItem>
|
||||
{
|
||||
TotalRecordCount = result.TotalRecordCount,
|
||||
Items = serverItems
|
||||
};
|
||||
return new QueryResult<ServerItem>(
|
||||
startIndex,
|
||||
result.TotalRecordCount,
|
||||
serverItems);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a query result to a <see cref="QueryResult{ServerItem}"/>.
|
||||
/// </summary>
|
||||
/// <param name="startIndex">The start index.</param>
|
||||
/// <param name="result">A <see cref="QueryResult{BaseItem}"/>.</param>
|
||||
/// <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 serverItems = new ServerItem[length];
|
||||
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>
|
||||
{
|
||||
TotalRecordCount = result.TotalRecordCount,
|
||||
Items = serverItems
|
||||
};
|
||||
return new QueryResult<ServerItem>(
|
||||
startIndex,
|
||||
result.TotalRecordCount,
|
||||
serverItems);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1213,7 +1209,7 @@ namespace Emby.Dlna.ContentDirectory
|
||||
/// </summary>
|
||||
/// <param name="sort">The <see cref="SortCriteria"/>.</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) };
|
||||
}
|
||||
|
||||
@@ -160,7 +160,7 @@ namespace Emby.Dlna.Didl
|
||||
else
|
||||
{
|
||||
var parent = item.DisplayParentId;
|
||||
if (!parent.Equals(Guid.Empty))
|
||||
if (!parent.Equals(default))
|
||||
{
|
||||
writer.WriteAttributeString("parentID", GetClientId(parent, null));
|
||||
}
|
||||
@@ -221,6 +221,7 @@ namespace Emby.Dlna.Didl
|
||||
streamInfo.IsDirectStream,
|
||||
streamInfo.RunTimeTicks ?? 0,
|
||||
streamInfo.TargetVideoProfile,
|
||||
streamInfo.TargetVideoRangeType,
|
||||
streamInfo.TargetVideoLevel,
|
||||
streamInfo.TargetFramerate ?? 0,
|
||||
streamInfo.TargetPacketLength,
|
||||
@@ -376,6 +377,7 @@ namespace Emby.Dlna.Didl
|
||||
targetHeight,
|
||||
streamInfo.TargetVideoBitDepth,
|
||||
streamInfo.TargetVideoProfile,
|
||||
streamInfo.TargetVideoRangeType,
|
||||
streamInfo.TargetVideoLevel,
|
||||
streamInfo.TargetFramerate ?? 0,
|
||||
streamInfo.TargetPacketLength,
|
||||
@@ -657,7 +659,7 @@ namespace Emby.Dlna.Didl
|
||||
else
|
||||
{
|
||||
var parent = folder.DisplayParentId;
|
||||
if (parent.Equals(Guid.Empty))
|
||||
if (parent.Equals(default))
|
||||
{
|
||||
writer.WriteAttributeString("parentID", "0");
|
||||
}
|
||||
@@ -989,7 +991,7 @@ namespace Emby.Dlna.Didl
|
||||
writer.WriteAttributeString("dlna", "profileID", NsDlna, _profile.AlbumArtPn);
|
||||
}
|
||||
|
||||
writer.WriteString(albumArtUrlInfo.url);
|
||||
writer.WriteString(albumArtUrlInfo.Url);
|
||||
writer.WriteFullEndElement();
|
||||
|
||||
// TODO: Remove these default values
|
||||
@@ -998,7 +1000,7 @@ namespace Emby.Dlna.Didl
|
||||
_profile.MaxIconWidth ?? 48,
|
||||
_profile.MaxIconHeight ?? 48,
|
||||
"jpg");
|
||||
writer.WriteElementString("upnp", "icon", NsUpnp, iconUrlInfo.url);
|
||||
writer.WriteElementString("upnp", "icon", NsUpnp, iconUrlInfo.Url);
|
||||
|
||||
if (!_profile.EnableAlbumArtInDidl)
|
||||
{
|
||||
@@ -1045,8 +1047,8 @@ namespace Emby.Dlna.Didl
|
||||
|
||||
// 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
|
||||
var width = albumartUrlInfo.width ?? maxWidth;
|
||||
var height = albumartUrlInfo.height ?? maxHeight;
|
||||
var width = albumartUrlInfo.Width ?? maxWidth;
|
||||
var height = albumartUrlInfo.Height ?? maxHeight;
|
||||
|
||||
var contentFeatures = ContentFeatureBuilder.BuildImageHeader(_profile, format, width, height, imageInfo.IsDirectStream, org_Pn);
|
||||
|
||||
@@ -1062,7 +1064,7 @@ namespace Emby.Dlna.Didl
|
||||
"resolution",
|
||||
string.Format(CultureInfo.InvariantCulture, "{0}x{1}", width, height));
|
||||
|
||||
writer.WriteString(albumartUrlInfo.url);
|
||||
writer.WriteString(albumartUrlInfo.Url);
|
||||
|
||||
writer.WriteFullEndElement();
|
||||
}
|
||||
@@ -1200,7 +1202,7 @@ namespace Emby.Dlna.Didl
|
||||
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(
|
||||
CultureInfo.InvariantCulture,
|
||||
|
||||
@@ -343,7 +343,8 @@ namespace Emby.Dlna
|
||||
var fileOptions = AsyncFile.WriteOptions;
|
||||
fileOptions.Mode = FileMode.Create;
|
||||
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);
|
||||
}
|
||||
@@ -455,7 +456,7 @@ namespace Emby.Dlna
|
||||
/// <inheritdoc />
|
||||
public string GetServerDescriptionXml(IHeaderDictionary headers, string serverUuId, string serverAddress)
|
||||
{
|
||||
var profile = GetDefaultProfile();
|
||||
var profile = GetProfile(headers) ?? GetDefaultProfile();
|
||||
|
||||
var serverId = _appHost.SystemId;
|
||||
|
||||
|
||||
@@ -28,8 +28,12 @@
|
||||
|
||||
<!-- Code Analyzers-->
|
||||
<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="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.406" PrivateAssets="All" />
|
||||
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -313,7 +313,7 @@ namespace Emby.Dlna.Main
|
||||
|
||||
_logger.LogInformation("Registering publisher for {ResourceName} on {DeviceAddress}", fullService, address);
|
||||
|
||||
var uri = new UriBuilder(_appHost.GetApiUrlForLocalAccess(false) + descriptorUri);
|
||||
var uri = new UriBuilder(_appHost.GetApiUrlForLocalAccess(address, false) + descriptorUri);
|
||||
|
||||
var device = new SsdpRootDevice
|
||||
{
|
||||
@@ -362,7 +362,7 @@ namespace Emby.Dlna.Main
|
||||
guid = text.GetMD5();
|
||||
}
|
||||
|
||||
return guid.ToString("N", CultureInfo.InvariantCulture);
|
||||
return guid.ToString("D", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
private void SetProperies(SsdpDevice device, string fullDeviceType)
|
||||
|
||||
@@ -69,11 +69,11 @@ namespace Emby.Dlna.PlayTo
|
||||
|
||||
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; }
|
||||
|
||||
@@ -494,7 +494,7 @@ namespace Emby.Dlna.PlayTo
|
||||
cancellationToken: cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
TransportState = TransportState.Paused;
|
||||
TransportState = TransportState.PAUSED_PLAYBACK;
|
||||
|
||||
RestartTimer(true);
|
||||
}
|
||||
@@ -527,7 +527,7 @@ namespace Emby.Dlna.PlayTo
|
||||
if (transportState.HasValue)
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
@@ -535,9 +535,9 @@ namespace Emby.Dlna.PlayTo
|
||||
{
|
||||
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);
|
||||
}
|
||||
@@ -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 (transportState.Value == TransportState.Stopped)
|
||||
if (transportState.Value == TransportState.STOPPED)
|
||||
{
|
||||
RestartTimerInactive();
|
||||
}
|
||||
@@ -797,7 +797,7 @@ namespace Emby.Dlna.PlayTo
|
||||
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");
|
||||
if (command == null)
|
||||
@@ -1229,7 +1229,7 @@ namespace Emby.Dlna.PlayTo
|
||||
}
|
||||
else if (previousMediaInfo == null)
|
||||
{
|
||||
if (state != TransportState.Stopped)
|
||||
if (state != TransportState.STOPPED)
|
||||
{
|
||||
OnPlaybackStart(mediaInfo);
|
||||
}
|
||||
|
||||
@@ -174,13 +174,13 @@ namespace Emby.Dlna.PlayTo
|
||||
await _sessionManager.OnPlaybackStart(newItemProgress).ConfigureAwait(false);
|
||||
|
||||
// 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)
|
||||
{
|
||||
_currentPlaylistIndex = currentItemIndex;
|
||||
}
|
||||
|
||||
await SendNextTrackMessage(currentItemIndex, CancellationToken.None);
|
||||
await SendNextTrackMessage(currentItemIndex, CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -349,7 +349,9 @@ namespace Emby.Dlna.PlayTo
|
||||
{
|
||||
_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>();
|
||||
foreach (var id in command.ItemIds)
|
||||
@@ -392,7 +394,7 @@ namespace Emby.Dlna.PlayTo
|
||||
_playlist.AddRange(playlist);
|
||||
}
|
||||
|
||||
if (!command.ControllingUserId.Equals(Guid.Empty))
|
||||
if (!command.ControllingUserId.Equals(default))
|
||||
{
|
||||
_sessionManager.LogSessionActivity(
|
||||
_session.Client,
|
||||
@@ -446,14 +448,16 @@ namespace Emby.Dlna.PlayTo
|
||||
|
||||
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);
|
||||
|
||||
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.
|
||||
var newItemIndex = _playlist.FindIndex(item => item.StreamUrl == newItem.StreamUrl);
|
||||
await SendNextTrackMessage(newItemIndex, CancellationToken.None);
|
||||
await SendNextTrackMessage(newItemIndex, CancellationToken.None).ConfigureAwait(false);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -557,6 +561,7 @@ namespace Emby.Dlna.PlayTo
|
||||
streamInfo.IsDirectStream,
|
||||
streamInfo.RunTimeTicks ?? 0,
|
||||
streamInfo.TargetVideoProfile,
|
||||
streamInfo.TargetVideoRangeType,
|
||||
streamInfo.TargetVideoLevel,
|
||||
streamInfo.TargetFramerate ?? 0,
|
||||
streamInfo.TargetPacketLength,
|
||||
@@ -569,7 +574,7 @@ namespace Emby.Dlna.PlayTo
|
||||
streamInfo.TargetVideoCodecTag,
|
||||
streamInfo.IsTargetAVC);
|
||||
|
||||
return list.Count == 0 ? null : list[0];
|
||||
return list.FirstOrDefault();
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -654,7 +659,7 @@ namespace Emby.Dlna.PlayTo
|
||||
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.
|
||||
await SendNextTrackMessage(index, cancellationToken);
|
||||
await SendNextTrackMessage(index, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var streamInfo = currentitem.StreamInfo;
|
||||
if (streamInfo.StartPositionTicks > 0 && EnableClientSideSeek(streamInfo))
|
||||
@@ -764,14 +769,16 @@ namespace Emby.Dlna.PlayTo
|
||||
{
|
||||
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);
|
||||
|
||||
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.
|
||||
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))
|
||||
{
|
||||
@@ -793,14 +800,16 @@ namespace Emby.Dlna.PlayTo
|
||||
{
|
||||
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);
|
||||
|
||||
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.
|
||||
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)
|
||||
{
|
||||
@@ -816,7 +825,7 @@ namespace Emby.Dlna.PlayTo
|
||||
const int Interval = 500;
|
||||
|
||||
var currentWait = 0;
|
||||
while (_device.TransportState != TransportState.Playing && currentWait < MaxWait)
|
||||
while (_device.TransportState != TransportState.PLAYING && currentWait < MaxWait)
|
||||
{
|
||||
await Task.Delay(Interval, cancellationToken).ConfigureAwait(false);
|
||||
currentWait += Interval;
|
||||
@@ -883,7 +892,7 @@ namespace Emby.Dlna.PlayTo
|
||||
|
||||
private class StreamParams
|
||||
{
|
||||
private MediaSourceInfo mediaSource;
|
||||
private MediaSourceInfo _mediaSource;
|
||||
private IMediaSourceManager _mediaSourceManager;
|
||||
|
||||
public Guid ItemId { get; set; }
|
||||
@@ -908,24 +917,22 @@ namespace Emby.Dlna.PlayTo
|
||||
|
||||
public async Task<MediaSourceInfo> GetMediaSource(CancellationToken cancellationToken)
|
||||
{
|
||||
if (mediaSource != null)
|
||||
if (_mediaSource != null)
|
||||
{
|
||||
return mediaSource;
|
||||
return _mediaSource;
|
||||
}
|
||||
|
||||
var hasMediaSources = Item as IHasMediaSources;
|
||||
|
||||
if (hasMediaSources == null)
|
||||
if (Item is not IHasMediaSources)
|
||||
{
|
||||
return 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)
|
||||
@@ -951,7 +958,7 @@ namespace Emby.Dlna.PlayTo
|
||||
}
|
||||
}
|
||||
|
||||
return Guid.Empty;
|
||||
return default;
|
||||
}
|
||||
|
||||
public static StreamParams ParseFromUrl(string url, ILibraryManager libraryManager, IMediaSourceManager mediaSourceManager)
|
||||
@@ -966,7 +973,7 @@ namespace Emby.Dlna.PlayTo
|
||||
ItemId = GetItemId(url)
|
||||
};
|
||||
|
||||
if (request.ItemId.Equals(Guid.Empty))
|
||||
if (request.ItemId.Equals(default))
|
||||
{
|
||||
return request;
|
||||
}
|
||||
|
||||
@@ -2,12 +2,15 @@
|
||||
|
||||
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
|
||||
{
|
||||
Stopped,
|
||||
Playing,
|
||||
Transitioning,
|
||||
PausedPlayback,
|
||||
Paused
|
||||
STOPPED,
|
||||
PLAYING,
|
||||
TRANSITIONING,
|
||||
PAUSED_PLAYBACK
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml;
|
||||
using Diacritics.Extensions;
|
||||
using Emby.Dlna.Didl;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
@@ -47,7 +47,7 @@ namespace Emby.Dlna.Service
|
||||
|
||||
private async Task<ControlResponse> ProcessControlRequestInternalAsync(ControlRequest request)
|
||||
{
|
||||
ControlRequestInfo? requestInfo = null;
|
||||
ControlRequestInfo requestInfo;
|
||||
|
||||
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);
|
||||
|
||||
return CreateControlResponse(requestInfo);
|
||||
}
|
||||
|
||||
private ControlResponse CreateControlResponse(ControlRequestInfo requestInfo)
|
||||
{
|
||||
var settings = new XmlWriterSettings
|
||||
{
|
||||
Encoding = Encoding.UTF8,
|
||||
@@ -112,29 +117,19 @@ namespace Emby.Dlna.Service
|
||||
{
|
||||
if (reader.NodeType == XmlNodeType.Element)
|
||||
{
|
||||
switch (reader.LocalName)
|
||||
if (string.Equals(reader.LocalName, "Body", StringComparison.Ordinal))
|
||||
{
|
||||
case "Body":
|
||||
{
|
||||
if (!reader.IsEmptyElement)
|
||||
{
|
||||
using var subReader = reader.ReadSubtree();
|
||||
return await ParseBodyTagAsync(subReader).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await reader.ReadAsync().ConfigureAwait(false);
|
||||
}
|
||||
if (reader.IsEmptyElement)
|
||||
{
|
||||
await reader.ReadAsync().ConfigureAwait(false);
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
await reader.SkipAsync().ConfigureAwait(false);
|
||||
break;
|
||||
}
|
||||
using var subReader = reader.ReadSubtree();
|
||||
return await ParseBodyTagAsync(subReader).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
await reader.SkipAsync().ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -160,17 +155,17 @@ namespace Emby.Dlna.Service
|
||||
localName = reader.LocalName;
|
||||
namespaceURI = reader.NamespaceURI;
|
||||
|
||||
if (!reader.IsEmptyElement)
|
||||
if (reader.IsEmptyElement)
|
||||
{
|
||||
await reader.ReadAsync().ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
var result = new ControlRequestInfo(localName, namespaceURI);
|
||||
using var subReader = reader.ReadSubtree();
|
||||
await ParseFirstBodyChildAsync(subReader, result.Headers).ConfigureAwait(false);
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
await reader.ReadAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -27,8 +27,12 @@
|
||||
|
||||
<!-- Code analysers-->
|
||||
<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="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.406" PrivateAssets="All" />
|
||||
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -395,7 +395,13 @@ namespace Emby.Drawing
|
||||
public string GetImageBlurHash(string path)
|
||||
{
|
||||
var size = GetImageDimensions(path);
|
||||
if (size.Width <= 0 || size.Height <= 0)
|
||||
return GetImageBlurHash(path, size);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetImageBlurHash(string path, ImageDimensions imageDimensions)
|
||||
{
|
||||
if (imageDimensions.Width <= 0 || imageDimensions.Height <= 0)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
@@ -403,8 +409,8 @@ namespace Emby.Drawing
|
||||
// We want tiles to be as close to square as possible, and to *mostly* keep under 16 tiles for performance.
|
||||
// One tile is (width / xComp) x (height / yComp) pixels, which means that ideally yComp = xComp * height / width.
|
||||
// See more at https://github.com/woltapp/blurhash/#how-do-i-pick-the-number-of-x-and-y-components
|
||||
float xCompF = MathF.Sqrt(16.0f * size.Width / size.Height);
|
||||
float yCompF = xCompF * size.Height / size.Width;
|
||||
float xCompF = MathF.Sqrt(16.0f * imageDimensions.Width / imageDimensions.Height);
|
||||
float yCompF = xCompF * imageDimensions.Height / imageDimensions.Width;
|
||||
|
||||
int xComp = Math.Min((int)xCompF + 1, 9);
|
||||
int yComp = Math.Min((int)yCompF + 1, 9);
|
||||
@@ -439,47 +445,46 @@ namespace Emby.Drawing
|
||||
.ToString("N", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
private async Task<(string Path, DateTime DateModified)> GetSupportedImage(string originalImagePath, DateTime dateModified)
|
||||
private Task<(string Path, DateTime DateModified)> GetSupportedImage(string originalImagePath, DateTime dateModified)
|
||||
{
|
||||
var inputFormat = Path.GetExtension(originalImagePath)
|
||||
.TrimStart('.')
|
||||
.Replace("jpeg", "jpg", StringComparison.OrdinalIgnoreCase);
|
||||
var inputFormat = Path.GetExtension(originalImagePath.AsSpan()).TrimStart('.').ToString();
|
||||
|
||||
// These are just jpg files renamed as tbn
|
||||
if (string.Equals(inputFormat, "tbn", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return (originalImagePath, dateModified);
|
||||
return Task.FromResult((originalImagePath, dateModified));
|
||||
}
|
||||
|
||||
if (!_imageEncoder.SupportedInputFormats.Contains(inputFormat))
|
||||
{
|
||||
try
|
||||
{
|
||||
string filename = (originalImagePath + dateModified.Ticks.ToString(CultureInfo.InvariantCulture)).GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
||||
// TODO _mediaEncoder.ConvertImage is not implemented
|
||||
// if (!_imageEncoder.SupportedInputFormats.Contains(inputFormat))
|
||||
// {
|
||||
// try
|
||||
// {
|
||||
// string filename = (originalImagePath + dateModified.Ticks.ToString(CultureInfo.InvariantCulture)).GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
||||
//
|
||||
// string cacheExtension = _mediaEncoder.SupportsEncoder("libwebp") ? ".webp" : ".png";
|
||||
// var outputPath = Path.Combine(_appPaths.ImageCachePath, "converted-images", filename + cacheExtension);
|
||||
//
|
||||
// var file = _fileSystem.GetFileInfo(outputPath);
|
||||
// if (!file.Exists)
|
||||
// {
|
||||
// await _mediaEncoder.ConvertImage(originalImagePath, outputPath).ConfigureAwait(false);
|
||||
// dateModified = _fileSystem.GetLastWriteTimeUtc(outputPath);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// dateModified = file.LastWriteTimeUtc;
|
||||
// }
|
||||
//
|
||||
// originalImagePath = outputPath;
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// _logger.LogError(ex, "Image conversion failed for {Path}", originalImagePath);
|
||||
// }
|
||||
// }
|
||||
|
||||
string cacheExtension = _mediaEncoder.SupportsEncoder("libwebp") ? ".webp" : ".png";
|
||||
var outputPath = Path.Combine(_appPaths.ImageCachePath, "converted-images", filename + cacheExtension);
|
||||
|
||||
var file = _fileSystem.GetFileInfo(outputPath);
|
||||
if (!file.Exists)
|
||||
{
|
||||
await _mediaEncoder.ConvertImage(originalImagePath, outputPath).ConfigureAwait(false);
|
||||
dateModified = _fileSystem.GetLastWriteTimeUtc(outputPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
dateModified = file.LastWriteTimeUtc;
|
||||
}
|
||||
|
||||
originalImagePath = outputPath;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Image conversion failed for {Path}", originalImagePath);
|
||||
}
|
||||
}
|
||||
|
||||
return (originalImagePath, dateModified);
|
||||
return Task.FromResult((originalImagePath, dateModified));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -43,6 +43,12 @@ namespace Emby.Drawing
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void CreateSplashscreen(IReadOnlyList<string> posters, IReadOnlyList<string> backdrops)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetImageBlurHash(int xComp, int yComp, string path)
|
||||
{
|
||||
|
||||
@@ -23,47 +23,60 @@ namespace Emby.Naming.Common
|
||||
{
|
||||
VideoFileExtensions = new[]
|
||||
{
|
||||
".m4v",
|
||||
".001",
|
||||
".3g2",
|
||||
".3gp",
|
||||
".nsv",
|
||||
".ts",
|
||||
".ty",
|
||||
".strm",
|
||||
".rm",
|
||||
".rmvb",
|
||||
".ifo",
|
||||
".mov",
|
||||
".qt",
|
||||
".divx",
|
||||
".xvid",
|
||||
".bivx",
|
||||
".vob",
|
||||
".nrg",
|
||||
".img",
|
||||
".iso",
|
||||
".pva",
|
||||
".wmv",
|
||||
".amv",
|
||||
".asf",
|
||||
".asx",
|
||||
".ogm",
|
||||
".m2v",
|
||||
".avi",
|
||||
".bin",
|
||||
".dvr-ms",
|
||||
".mpg",
|
||||
".mpeg",
|
||||
".mp4",
|
||||
".mkv",
|
||||
".avc",
|
||||
".vp3",
|
||||
".svq3",
|
||||
".nuv",
|
||||
".viv",
|
||||
".bivx",
|
||||
".divx",
|
||||
".dv",
|
||||
".dvr-ms",
|
||||
".f4v",
|
||||
".fli",
|
||||
".flv",
|
||||
".001",
|
||||
".tp"
|
||||
".ifo",
|
||||
".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[]
|
||||
@@ -149,32 +162,20 @@ namespace Emby.Naming.Common
|
||||
|
||||
SubtitleFileExtensions = new[]
|
||||
{
|
||||
".ass",
|
||||
".mks",
|
||||
".sami",
|
||||
".smi",
|
||||
".srt",
|
||||
".ssa",
|
||||
".ass",
|
||||
".sub"
|
||||
};
|
||||
|
||||
SubtitleFlagDelimiters = new[]
|
||||
{
|
||||
'.'
|
||||
};
|
||||
|
||||
SubtitleForcedFlags = new[]
|
||||
{
|
||||
"foreign",
|
||||
"forced"
|
||||
};
|
||||
|
||||
SubtitleDefaultFlags = new[]
|
||||
{
|
||||
"default"
|
||||
".sub",
|
||||
".vtt",
|
||||
};
|
||||
|
||||
AlbumStackingPrefixes = new[]
|
||||
{
|
||||
"disc",
|
||||
"cd",
|
||||
"disc",
|
||||
"disk",
|
||||
"vol",
|
||||
"volume"
|
||||
@@ -182,68 +183,101 @@ namespace Emby.Naming.Common
|
||||
|
||||
AudioFileExtensions = new[]
|
||||
{
|
||||
".nsv",
|
||||
".m4a",
|
||||
".flac",
|
||||
".aac",
|
||||
".strm",
|
||||
".pls",
|
||||
".rm",
|
||||
".mpa",
|
||||
".wav",
|
||||
".wma",
|
||||
".ogg",
|
||||
".opus",
|
||||
".mp3",
|
||||
".mp2",
|
||||
".mod",
|
||||
".amf",
|
||||
".669",
|
||||
".3gp",
|
||||
".aa",
|
||||
".aac",
|
||||
".aax",
|
||||
".ac3",
|
||||
".act",
|
||||
".adp",
|
||||
".adplug",
|
||||
".adx",
|
||||
".afc",
|
||||
".amf",
|
||||
".aif",
|
||||
".aiff",
|
||||
".alac",
|
||||
".amr",
|
||||
".ape",
|
||||
".ast",
|
||||
".au",
|
||||
".awb",
|
||||
".cda",
|
||||
".cue",
|
||||
".dmf",
|
||||
".dsf",
|
||||
".dsm",
|
||||
".dsp",
|
||||
".dts",
|
||||
".dvf",
|
||||
".far",
|
||||
".flac",
|
||||
".gdm",
|
||||
".gsm",
|
||||
".gym",
|
||||
".hps",
|
||||
".imf",
|
||||
".it",
|
||||
".m15",
|
||||
".m4a",
|
||||
".m4b",
|
||||
".mac",
|
||||
".med",
|
||||
".mka",
|
||||
".mmf",
|
||||
".mod",
|
||||
".mogg",
|
||||
".mp2",
|
||||
".mp3",
|
||||
".mpa",
|
||||
".mpc",
|
||||
".mpp",
|
||||
".mp+",
|
||||
".msv",
|
||||
".nmf",
|
||||
".nsf",
|
||||
".nsv",
|
||||
".oga",
|
||||
".ogg",
|
||||
".okt",
|
||||
".opus",
|
||||
".pls",
|
||||
".ra",
|
||||
".rf64",
|
||||
".rm",
|
||||
".s3m",
|
||||
".stm",
|
||||
".sfx",
|
||||
".shn",
|
||||
".sid",
|
||||
".spc",
|
||||
".stm",
|
||||
".strm",
|
||||
".ult",
|
||||
".uni",
|
||||
".xm",
|
||||
".sid",
|
||||
".ac3",
|
||||
".dts",
|
||||
".cue",
|
||||
".aif",
|
||||
".aiff",
|
||||
".ape",
|
||||
".mac",
|
||||
".mpc",
|
||||
".mp+",
|
||||
".mpp",
|
||||
".shn",
|
||||
".vox",
|
||||
".wav",
|
||||
".wma",
|
||||
".wv",
|
||||
".nsf",
|
||||
".spc",
|
||||
".gym",
|
||||
".adplug",
|
||||
".adx",
|
||||
".dsp",
|
||||
".adp",
|
||||
".ymf",
|
||||
".ast",
|
||||
".afc",
|
||||
".hps",
|
||||
".xm",
|
||||
".xsp",
|
||||
".acc",
|
||||
".m4b",
|
||||
".oga",
|
||||
".dsf",
|
||||
".mka"
|
||||
".ymf"
|
||||
};
|
||||
|
||||
MediaFlagDelimiters = new[]
|
||||
{
|
||||
'.'
|
||||
};
|
||||
|
||||
MediaForcedFlags = new[]
|
||||
{
|
||||
"foreign",
|
||||
"forced"
|
||||
};
|
||||
|
||||
MediaDefaultFlags = new[]
|
||||
{
|
||||
"default"
|
||||
};
|
||||
|
||||
EpisodeExpressions = new[]
|
||||
@@ -280,7 +314,7 @@ namespace Emby.Naming.Common
|
||||
// This isn't a Kodi naming rule, but the expression below causes false positives,
|
||||
// so we make sure this one gets tested first.
|
||||
// "Foo Bar 889"
|
||||
new EpisodeExpression(@".*[\\\/](?![Ee]pisode)(?<seriesname>[\w\s]+?)\s(?<epnumber>[0-9]{1,3})(-(?<endingepnumber>[0-9]{2,3}))*[^\\\/x]*$")
|
||||
new EpisodeExpression(@".*[\\\/](?![Ee]pisode)(?<seriesname>[\w\s]+?)\s(?<epnumber>[0-9]{1,4})(-(?<endingepnumber>[0-9]{2,4}))*[^\\\/x]*$")
|
||||
{
|
||||
IsNamed = true
|
||||
},
|
||||
@@ -410,6 +444,66 @@ namespace Emby.Naming.Common
|
||||
"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(
|
||||
ExtraType.Trailer,
|
||||
ExtraRuleType.Filename,
|
||||
@@ -470,24 +564,12 @@ namespace Emby.Naming.Common
|
||||
" sample",
|
||||
MediaType.Video),
|
||||
|
||||
new ExtraRule(
|
||||
ExtraType.ThemeVideo,
|
||||
ExtraRuleType.DirectoryName,
|
||||
"backdrops",
|
||||
MediaType.Video),
|
||||
|
||||
new ExtraRule(
|
||||
ExtraType.ThemeSong,
|
||||
ExtraRuleType.Filename,
|
||||
"theme",
|
||||
MediaType.Audio),
|
||||
|
||||
new ExtraRule(
|
||||
ExtraType.ThemeSong,
|
||||
ExtraRuleType.DirectoryName,
|
||||
"theme-music",
|
||||
MediaType.Audio),
|
||||
|
||||
new ExtraRule(
|
||||
ExtraType.Scene,
|
||||
ExtraRuleType.Suffix,
|
||||
@@ -536,55 +618,17 @@ namespace Emby.Naming.Common
|
||||
"-short",
|
||||
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(
|
||||
ExtraType.Unknown,
|
||||
ExtraRuleType.DirectoryName,
|
||||
"extras",
|
||||
ExtraRuleType.Suffix,
|
||||
"-extra",
|
||||
MediaType.Video)
|
||||
};
|
||||
|
||||
AllExtrasTypesFolderNames = VideoExtraRules
|
||||
.Where(i => i.RuleType == ExtraRuleType.DirectoryName)
|
||||
.ToDictionary(i => i.Token, i => i.ExtraType, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
Format3DRules = new[]
|
||||
{
|
||||
// Kodi rules:
|
||||
@@ -638,41 +682,6 @@ namespace Emby.Naming.Common
|
||||
@"^\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[]
|
||||
{
|
||||
@".*(\\|\/)[sS]?(?<seasonnumber>[0-9]{1,4})[xX](?<epnumber>[0-9]{1,3})((-| - )[0-9]{1,4}[eExX](?<endingepnumber>[0-9]{1,3}))+[^\\\/]*$",
|
||||
@@ -690,25 +699,6 @@ namespace Emby.Naming.Common
|
||||
IsNamed = true
|
||||
}).ToArray();
|
||||
|
||||
VideoFileExtensions = extensions
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.ToArray();
|
||||
|
||||
AllExtrasTypesFolderNames = new Dictionary<string, ExtraType>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["trailers"] = ExtraType.Trailer,
|
||||
["theme-music"] = ExtraType.ThemeSong,
|
||||
["backdrops"] = ExtraType.ThemeVideo,
|
||||
["extras"] = ExtraType.Unknown,
|
||||
["behind the scenes"] = ExtraType.BehindTheScenes,
|
||||
["deleted scenes"] = ExtraType.DeletedScene,
|
||||
["interviews"] = ExtraType.Interview,
|
||||
["scenes"] = ExtraType.Scene,
|
||||
["samples"] = ExtraType.Sample,
|
||||
["shorts"] = ExtraType.Clip,
|
||||
["featurettes"] = ExtraType.Clip
|
||||
};
|
||||
|
||||
Compile();
|
||||
}
|
||||
|
||||
@@ -722,6 +712,21 @@ namespace Emby.Naming.Common
|
||||
/// </summary>
|
||||
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>
|
||||
/// Gets or sets list of album stacking prefixes.
|
||||
/// </summary>
|
||||
@@ -732,21 +737,6 @@ namespace Emby.Naming.Common
|
||||
/// </summary>
|
||||
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>
|
||||
/// Gets or sets list of episode regular expressions.
|
||||
/// </summary>
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
<PropertyGroup>
|
||||
<Authors>Jellyfin Contributors</Authors>
|
||||
<PackageId>Jellyfin.Naming</PackageId>
|
||||
<VersionPrefix>10.8.0</VersionPrefix>
|
||||
<VersionPrefix>10.8.9</VersionPrefix>
|
||||
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
||||
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
||||
</PropertyGroup>
|
||||
@@ -47,8 +47,12 @@
|
||||
|
||||
<!-- Code Analyzers-->
|
||||
<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="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.406" PrivateAssets="All" />
|
||||
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
|
||||
</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>
|
||||
/// Class holding information about subtitle.
|
||||
/// Class holding information about external files.
|
||||
/// </summary>
|
||||
public class SubtitleInfo
|
||||
public class ExternalPathParserResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SubtitleInfo"/> class.
|
||||
/// Initializes a new instance of the <see cref="ExternalPathParserResult"/> class.
|
||||
/// </summary>
|
||||
/// <param name="path">Path to file.</param>
|
||||
/// <param name="isDefault">Is subtitle default.</param>
|
||||
/// <param name="isForced">Is subtitle forced.</param>
|
||||
public SubtitleInfo(string path, bool isDefault, bool isForced)
|
||||
/// <param name="isDefault">Is default.</param>
|
||||
/// <param name="isForced">Is forced.</param>
|
||||
public ExternalPathParserResult(string path, bool isDefault = false, bool isForced = false)
|
||||
{
|
||||
Path = path;
|
||||
IsDefault = isDefault;
|
||||
@@ -30,6 +30,12 @@ namespace Emby.Naming.Subtitles
|
||||
/// <value>The language.</value>
|
||||
public string? Language { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the title.
|
||||
/// </summary>
|
||||
/// <value>The title.</value>
|
||||
public string? Title { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this instance is default.
|
||||
/// </summary>
|
||||
@@ -1,71 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Emby.Naming.Common;
|
||||
using Jellyfin.Extensions;
|
||||
|
||||
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, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var flags = GetFlags(path);
|
||||
var info = new SubtitleInfo(
|
||||
path,
|
||||
_options.SubtitleDefaultFlags.Any(i => flags.Contains(i, StringComparison.OrdinalIgnoreCase)),
|
||||
_options.SubtitleForcedFlags.Any(i => flags.Contains(i, StringComparison.OrdinalIgnoreCase)));
|
||||
|
||||
var parts = flags.Where(i => !_options.SubtitleDefaultFlags.Contains(i, StringComparison.OrdinalIgnoreCase)
|
||||
&& !_options.SubtitleForcedFlags.Contains(i, StringComparison.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,8 +23,12 @@
|
||||
|
||||
<!-- Code analyzers-->
|
||||
<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="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.406" PrivateAssets="All" />
|
||||
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -112,7 +112,7 @@ namespace Emby.Notifications
|
||||
|
||||
var userId = e.Argument.UserId;
|
||||
|
||||
if (!userId.Equals(Guid.Empty) && !GetOptions().IsEnabledToMonitorUser(type, userId))
|
||||
if (!userId.Equals(default) && !GetOptions().IsEnabledToMonitorUser(type, userId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="TagLibSharp" Version="2.2.0" />
|
||||
<PackageReference Include="TagLibSharp" Version="2.3.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
@@ -26,7 +26,11 @@
|
||||
|
||||
<!-- Code Analyzers-->
|
||||
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" 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="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -398,6 +398,12 @@ namespace Emby.Server.Implementations.AppBase
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ConfigurationStore[] GetConfigurationStores()
|
||||
{
|
||||
return _configurationStores;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Type GetConfigurationType(string key)
|
||||
{
|
||||
|
||||
@@ -44,9 +44,9 @@ using Emby.Server.Implementations.Serialization;
|
||||
using Emby.Server.Implementations.Session;
|
||||
using Emby.Server.Implementations.SyncPlay;
|
||||
using Emby.Server.Implementations.TV;
|
||||
using Emby.Server.Implementations.Udp;
|
||||
using Emby.Server.Implementations.Updates;
|
||||
using Jellyfin.Api.Helpers;
|
||||
using Jellyfin.MediaEncoding.Hls.Playlist;
|
||||
using Jellyfin.Networking.Configuration;
|
||||
using Jellyfin.Networking.Manager;
|
||||
using MediaBrowser.Common;
|
||||
@@ -103,6 +103,7 @@ using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Prometheus.DotNetRuntime;
|
||||
using static MediaBrowser.Controller.Extensions.ConfigurationExtensions;
|
||||
using WebSocketManager = Emby.Server.Implementations.HttpServer.WebSocketManager;
|
||||
|
||||
namespace Emby.Server.Implementations
|
||||
@@ -110,7 +111,7 @@ namespace Emby.Server.Implementations
|
||||
/// <summary>
|
||||
/// Class CompositionRoot.
|
||||
/// </summary>
|
||||
public abstract class ApplicationHost : IServerApplicationHost, IDisposable
|
||||
public abstract class ApplicationHost : IServerApplicationHost, IAsyncDisposable, IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// The environment variable prefixes to log at server startup.
|
||||
@@ -149,7 +150,7 @@ namespace Emby.Server.Implementations
|
||||
/// <param name="loggerFactory">Instance of the <see cref="ILoggerFactory"/> interface.</param>
|
||||
/// <param name="options">Instance of the <see cref="IStartupOptions"/> interface.</param>
|
||||
/// <param name="startupConfig">The <see cref="IConfiguration" /> interface.</param>
|
||||
public ApplicationHost(
|
||||
protected ApplicationHost(
|
||||
IServerApplicationPaths applicationPaths,
|
||||
ILoggerFactory loggerFactory,
|
||||
IStartupOptions options,
|
||||
@@ -183,6 +184,11 @@ namespace Emby.Server.Implementations
|
||||
/// </summary>
|
||||
public event EventHandler HasPendingRestartChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value of the PublishedServerUrl setting.
|
||||
/// </summary>
|
||||
private string PublishedServerUrl => _startupConfig[AddressOverrideKey];
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance can self restart.
|
||||
/// </summary>
|
||||
@@ -259,11 +265,6 @@ namespace Emby.Server.Implementations
|
||||
/// </summary>
|
||||
public int HttpsPort { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value of the PublishedServerUrl setting.
|
||||
/// </summary>
|
||||
public string PublishedServerUrl => _startupOptions.PublishedServerUrl ?? _startupConfig[UdpServer.AddressOverrideConfigKey];
|
||||
|
||||
/// <inheritdoc />
|
||||
public Version ApplicationVersion { get; }
|
||||
|
||||
@@ -973,7 +974,7 @@ namespace Emby.Server.Implementations
|
||||
yield return typeof(IServerApplicationHost).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
|
||||
yield return typeof(PhotoProvider).Assembly;
|
||||
@@ -999,6 +1000,9 @@ namespace Emby.Server.Implementations
|
||||
// Network
|
||||
yield return typeof(NetworkManager).Assembly;
|
||||
|
||||
// Hls
|
||||
yield return typeof(DynamicHlsPlaylistGenerator).Assembly;
|
||||
|
||||
foreach (var i in GetAssembliesWithPartsInternal())
|
||||
{
|
||||
yield return i;
|
||||
@@ -1084,15 +1088,7 @@ namespace Emby.Server.Implementations
|
||||
return GetLocalApiUrl(request.Host.Host, request.Scheme, requestPort);
|
||||
}
|
||||
|
||||
// Published server ends with a /
|
||||
if (!string.IsNullOrEmpty(PublishedServerUrl))
|
||||
{
|
||||
// Published server ends with a '/', so we need to remove it.
|
||||
return PublishedServerUrl.Trim('/');
|
||||
}
|
||||
|
||||
string smart = NetManager.GetBindInterface(request, out var port);
|
||||
return GetLocalApiUrl(smart.Trim('/'), request.Scheme, port);
|
||||
return GetSmartApiUrl(request.HttpContext.Connection.RemoteIpAddress ?? IPAddress.Loopback);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -1110,13 +1106,13 @@ namespace Emby.Server.Implementations
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string GetApiUrlForLocalAccess(bool allowHttps = true)
|
||||
public string GetApiUrlForLocalAccess(IPObject hostname = null, bool allowHttps = true)
|
||||
{
|
||||
// With an empty source, the port will be null
|
||||
string smart = NetManager.GetBindInterface(string.Empty, out _);
|
||||
var smart = NetManager.GetBindInterface(hostname ?? IPHost.None, out _);
|
||||
var scheme = !allowHttps ? Uri.UriSchemeHttp : null;
|
||||
int? port = !allowHttps ? HttpPort : null;
|
||||
return GetLocalApiUrl(smart.Trim('/'), scheme, port);
|
||||
return GetLocalApiUrl(smart, scheme, port);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -1130,11 +1126,13 @@ namespace Emby.Server.Implementations
|
||||
|
||||
// NOTE: If no BaseUrl is set then UriBuilder appends a trailing slash, but if there is no BaseUrl it does
|
||||
// not. For consistency, always trim the trailing slash.
|
||||
scheme ??= ListenWithHttps ? Uri.UriSchemeHttps : Uri.UriSchemeHttp;
|
||||
var isHttps = string.Equals(scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase);
|
||||
return new UriBuilder
|
||||
{
|
||||
Scheme = scheme ?? (ListenWithHttps ? Uri.UriSchemeHttps : Uri.UriSchemeHttp),
|
||||
Scheme = scheme,
|
||||
Host = hostname,
|
||||
Port = port ?? (ListenWithHttps ? HttpsPort : HttpPort),
|
||||
Port = port ?? (isHttps ? HttpsPort : HttpPort),
|
||||
Path = ConfigurationManager.GetNetworkConfiguration().BaseUrl
|
||||
}.ToString().TrimEnd('/');
|
||||
}
|
||||
@@ -1226,5 +1224,49 @@ namespace Emby.Server.Implementations
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
await DisposeAsyncCore().ConfigureAwait(false);
|
||||
Dispose(false);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to perform asynchronous cleanup of managed resources or for cascading calls to <see cref="DisposeAsync"/>.
|
||||
/// </summary>
|
||||
/// <returns>A ValueTask.</returns>
|
||||
protected virtual async ValueTask DisposeAsyncCore()
|
||||
{
|
||||
var type = GetType();
|
||||
|
||||
Logger.LogInformation("Disposing {Type}", type.Name);
|
||||
|
||||
foreach (var (part, _) in _disposableParts)
|
||||
{
|
||||
var partType = part.GetType();
|
||||
if (partType == type)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Logger.LogInformation("Disposing {Type}", partType.Name);
|
||||
|
||||
try
|
||||
{
|
||||
part.Dispose();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Error disposing {Type}", partType.Name);
|
||||
}
|
||||
}
|
||||
|
||||
// used for closing websockets
|
||||
foreach (var session in _sessionManager.Sessions)
|
||||
{
|
||||
await session.DisposeAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ namespace Emby.Server.Implementations.Channels
|
||||
/// <summary>
|
||||
/// The LiveTV channel manager.
|
||||
/// </summary>
|
||||
public class ChannelManager : IChannelManager
|
||||
public class ChannelManager : IChannelManager, IDisposable
|
||||
{
|
||||
private readonly IUserManager _userManager;
|
||||
private readonly IUserDataManager _userDataManager;
|
||||
@@ -52,6 +52,7 @@ namespace Emby.Server.Implementations.Channels
|
||||
private readonly IMemoryCache _memoryCache;
|
||||
private readonly SemaphoreSlim _resourcePool = new SemaphoreSlim(1, 1);
|
||||
private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
|
||||
private bool _disposed = false;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ChannelManager"/> class.
|
||||
@@ -161,7 +162,7 @@ namespace Emby.Server.Implementations.Channels
|
||||
/// <inheritdoc />
|
||||
public QueryResult<Channel> GetChannelsInternal(ChannelQuery query)
|
||||
{
|
||||
var user = query.UserId.Equals(Guid.Empty)
|
||||
var user = query.UserId.Equals(default)
|
||||
? null
|
||||
: _userManager.GetUserById(query.UserId);
|
||||
|
||||
@@ -264,17 +265,16 @@ namespace Emby.Server.Implementations.Channels
|
||||
}
|
||||
}
|
||||
|
||||
return new QueryResult<Channel>
|
||||
{
|
||||
Items = all,
|
||||
TotalRecordCount = totalCount
|
||||
};
|
||||
return new QueryResult<Channel>(
|
||||
query.StartIndex,
|
||||
totalCount,
|
||||
all);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public QueryResult<BaseItemDto> GetChannels(ChannelQuery query)
|
||||
{
|
||||
var user = query.UserId.Equals(Guid.Empty)
|
||||
var user = query.UserId.Equals(default)
|
||||
? null
|
||||
: _userManager.GetUserById(query.UserId);
|
||||
|
||||
@@ -285,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.
|
||||
var returnItems = _dtoService.GetBaseItemDtos(internalResult.Items, dtoOptions, user);
|
||||
|
||||
var result = new QueryResult<BaseItemDto>
|
||||
{
|
||||
Items = returnItems,
|
||||
TotalRecordCount = internalResult.TotalRecordCount
|
||||
};
|
||||
var result = new QueryResult<BaseItemDto>(
|
||||
query.StartIndex,
|
||||
internalResult.TotalRecordCount,
|
||||
returnItems);
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -333,7 +332,7 @@ namespace Emby.Server.Implementations.Channels
|
||||
|
||||
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)
|
||||
@@ -475,7 +474,7 @@ namespace Emby.Server.Implementations.Channels
|
||||
|
||||
item.ChannelId = id;
|
||||
|
||||
if (item.ParentId != parentFolderId)
|
||||
if (!item.ParentId.Equals(parentFolderId))
|
||||
{
|
||||
forceUpdate = true;
|
||||
}
|
||||
@@ -620,11 +619,10 @@ namespace Emby.Server.Implementations.Channels
|
||||
|
||||
var returnItems = _dtoService.GetBaseItemDtos(items, query.DtoOptions, query.User);
|
||||
|
||||
var result = new QueryResult<BaseItemDto>
|
||||
{
|
||||
Items = returnItems,
|
||||
TotalRecordCount = totalRecordCount
|
||||
};
|
||||
var result = new QueryResult<BaseItemDto>(
|
||||
query.StartIndex,
|
||||
totalRecordCount,
|
||||
returnItems);
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -717,7 +715,9 @@ namespace Emby.Server.Implementations.Channels
|
||||
// Find the corresponding channel provider plugin
|
||||
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(
|
||||
channelProvider,
|
||||
@@ -728,7 +728,7 @@ namespace Emby.Server.Implementations.Channels
|
||||
cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (query.ParentId == Guid.Empty)
|
||||
if (query.ParentId.Equals(default))
|
||||
{
|
||||
query.Parent = channel;
|
||||
}
|
||||
@@ -786,11 +786,10 @@ namespace Emby.Server.Implementations.Channels
|
||||
|
||||
var returnItems = _dtoService.GetBaseItemDtos(internalResult.Items, query.DtoOptions, query.User);
|
||||
|
||||
var result = new QueryResult<BaseItemDto>
|
||||
{
|
||||
Items = returnItems,
|
||||
TotalRecordCount = internalResult.TotalRecordCount
|
||||
};
|
||||
var result = new QueryResult<BaseItemDto>(
|
||||
query.StartIndex,
|
||||
internalResult.TotalRecordCount,
|
||||
returnItems);
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -1217,5 +1216,31 @@ namespace Emby.Server.Implementations.Channels
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ namespace Emby.Server.Implementations.Channels
|
||||
public string Key => "RefreshInternetChannels";
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task Execute(CancellationToken cancellationToken, IProgress<double> progress)
|
||||
public async Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
|
||||
{
|
||||
var manager = (ChannelManager)_channelManager;
|
||||
|
||||
|
||||
@@ -265,7 +265,7 @@ namespace Emby.Server.Implementations.Collections
|
||||
{
|
||||
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)
|
||||
{
|
||||
|
||||
@@ -11,7 +11,6 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using Diacritics.Extensions;
|
||||
using Emby.Server.Implementations.Playlists;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Extensions;
|
||||
@@ -171,7 +170,15 @@ namespace Emby.Server.Implementations.Data
|
||||
"CodecTimeBase",
|
||||
"ColorPrimaries",
|
||||
"ColorSpace",
|
||||
"ColorTransfer"
|
||||
"ColorTransfer",
|
||||
"DvVersionMajor",
|
||||
"DvVersionMinor",
|
||||
"DvProfile",
|
||||
"DvLevel",
|
||||
"RpuPresentFlag",
|
||||
"ElPresentFlag",
|
||||
"BlPresentFlag",
|
||||
"DvBlSignalCompatibilityId"
|
||||
};
|
||||
|
||||
private static readonly string _mediaStreamSaveColumnsInsertQuery =
|
||||
@@ -317,11 +324,6 @@ namespace Emby.Server.Implementations.Data
|
||||
IImageProcessor imageProcessor)
|
||||
: base(logger)
|
||||
{
|
||||
if (config == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(config));
|
||||
}
|
||||
|
||||
_config = config;
|
||||
_appHost = appHost;
|
||||
_localization = localization;
|
||||
@@ -333,9 +335,6 @@ namespace Emby.Server.Implementations.Data
|
||||
DbFilePath = Path.Combine(_config.ApplicationPaths.DataPath, "library.db");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Name => "SQLite";
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override int? CacheSize => 20000;
|
||||
|
||||
@@ -350,7 +349,7 @@ namespace Emby.Server.Implementations.Data
|
||||
public void Initialize(SqliteUserDataRepository userDataRepo, IUserManager userManager)
|
||||
{
|
||||
const string CreateMediaStreamsTableCommand
|
||||
= "create table if not exists mediastreams (ItemId GUID, StreamIndex INT, StreamType TEXT, Codec TEXT, Language TEXT, ChannelLayout TEXT, Profile TEXT, AspectRatio TEXT, Path TEXT, IsInterlaced BIT, BitRate INT NULL, Channels INT NULL, SampleRate INT NULL, IsDefault BIT, IsForced BIT, IsExternal BIT, Height INT NULL, Width INT NULL, AverageFrameRate FLOAT NULL, RealFrameRate FLOAT NULL, Level FLOAT NULL, PixelFormat TEXT, BitDepth INT NULL, IsAnamorphic BIT NULL, RefFrames INT NULL, CodecTag TEXT NULL, Comment TEXT NULL, NalLengthSize TEXT NULL, IsAvc BIT NULL, Title TEXT NULL, TimeBase TEXT NULL, CodecTimeBase TEXT NULL, ColorPrimaries TEXT NULL, ColorSpace TEXT NULL, ColorTransfer TEXT NULL, PRIMARY KEY (ItemId, StreamIndex))";
|
||||
= "create table if not exists mediastreams (ItemId GUID, StreamIndex INT, StreamType TEXT, Codec TEXT, Language TEXT, ChannelLayout TEXT, Profile TEXT, AspectRatio TEXT, Path TEXT, IsInterlaced BIT, BitRate INT NULL, Channels INT NULL, SampleRate INT NULL, IsDefault BIT, IsForced BIT, IsExternal BIT, Height INT NULL, Width INT NULL, AverageFrameRate FLOAT NULL, RealFrameRate FLOAT NULL, Level FLOAT NULL, PixelFormat TEXT, BitDepth INT NULL, IsAnamorphic BIT NULL, RefFrames INT NULL, CodecTag TEXT NULL, Comment TEXT NULL, NalLengthSize TEXT NULL, IsAvc BIT NULL, Title TEXT NULL, TimeBase TEXT NULL, CodecTimeBase TEXT NULL, ColorPrimaries TEXT NULL, ColorSpace TEXT NULL, ColorTransfer TEXT NULL, DvVersionMajor INT NULL, DvVersionMinor INT NULL, DvProfile INT NULL, DvLevel INT NULL, RpuPresentFlag INT NULL, ElPresentFlag INT NULL, BlPresentFlag INT NULL, DvBlSignalCompatibilityId INT NULL, PRIMARY KEY (ItemId, StreamIndex))";
|
||||
const string CreateMediaAttachmentsTableCommand
|
||||
= "create table if not exists mediaattachments (ItemId GUID, AttachmentIndex INT, Codec TEXT, CodecTag TEXT NULL, Comment TEXT NULL, Filename TEXT NULL, MIMEType TEXT NULL, PRIMARY KEY (ItemId, AttachmentIndex))";
|
||||
|
||||
@@ -564,6 +563,15 @@ namespace Emby.Server.Implementations.Data
|
||||
AddColumn(db, "MediaStreams", "ColorPrimaries", "TEXT", existingColumnNames);
|
||||
AddColumn(db, "MediaStreams", "ColorSpace", "TEXT", existingColumnNames);
|
||||
AddColumn(db, "MediaStreams", "ColorTransfer", "TEXT", existingColumnNames);
|
||||
|
||||
AddColumn(db, "MediaStreams", "DvVersionMajor", "INT", existingColumnNames);
|
||||
AddColumn(db, "MediaStreams", "DvVersionMinor", "INT", existingColumnNames);
|
||||
AddColumn(db, "MediaStreams", "DvProfile", "INT", existingColumnNames);
|
||||
AddColumn(db, "MediaStreams", "DvLevel", "INT", existingColumnNames);
|
||||
AddColumn(db, "MediaStreams", "RpuPresentFlag", "INT", existingColumnNames);
|
||||
AddColumn(db, "MediaStreams", "ElPresentFlag", "INT", existingColumnNames);
|
||||
AddColumn(db, "MediaStreams", "BlPresentFlag", "INT", existingColumnNames);
|
||||
AddColumn(db, "MediaStreams", "DvBlSignalCompatibilityId", "INT", existingColumnNames);
|
||||
},
|
||||
TransactionMode);
|
||||
|
||||
@@ -573,22 +581,6 @@ namespace Emby.Server.Implementations.Data
|
||||
userDataRepo.Initialize(userManager, WriteLock, WriteConnection);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save a standard item in the repo.
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="item"/> is <c>null</c>.</exception>
|
||||
public void SaveItem(BaseItem item, CancellationToken cancellationToken)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(item));
|
||||
}
|
||||
|
||||
SaveItems(new[] { item }, cancellationToken);
|
||||
}
|
||||
|
||||
public void SaveImages(BaseItem item)
|
||||
{
|
||||
if (item == null)
|
||||
@@ -605,7 +597,7 @@ namespace Emby.Server.Implementations.Data
|
||||
{
|
||||
using (var saveImagesStatement = PrepareStatement(db, "Update TypedBaseItems set Images=@Images where guid=@Id"))
|
||||
{
|
||||
saveImagesStatement.TryBind("@Id", item.Id.ToByteArray());
|
||||
saveImagesStatement.TryBind("@Id", item.Id);
|
||||
saveImagesStatement.TryBind("@Images", SerializeImages(item.ImageInfos));
|
||||
|
||||
saveImagesStatement.MoveNext();
|
||||
@@ -750,7 +742,7 @@ namespace Emby.Server.Implementations.Data
|
||||
saveItemStatement.TryBindNull("@EndDate");
|
||||
}
|
||||
|
||||
saveItemStatement.TryBind("@ChannelId", item.ChannelId.Equals(Guid.Empty) ? null : item.ChannelId.ToString("N", CultureInfo.InvariantCulture));
|
||||
saveItemStatement.TryBind("@ChannelId", item.ChannelId.Equals(default) ? null : item.ChannelId.ToString("N", CultureInfo.InvariantCulture));
|
||||
|
||||
if (item is IHasProgramAttributes hasProgramAttributes)
|
||||
{
|
||||
@@ -780,7 +772,7 @@ namespace Emby.Server.Implementations.Data
|
||||
saveItemStatement.TryBind("@ProductionYear", item.ProductionYear);
|
||||
|
||||
var parentId = item.ParentId;
|
||||
if (parentId.Equals(Guid.Empty))
|
||||
if (parentId.Equals(default))
|
||||
{
|
||||
saveItemStatement.TryBindNull("@ParentId");
|
||||
}
|
||||
@@ -975,7 +967,7 @@ namespace Emby.Server.Implementations.Data
|
||||
{
|
||||
saveItemStatement.TryBind("@SeasonName", episode.SeasonName);
|
||||
|
||||
var nullableSeasonId = episode.SeasonId == Guid.Empty ? (Guid?)null : episode.SeasonId;
|
||||
var nullableSeasonId = episode.SeasonId.Equals(default) ? (Guid?)null : episode.SeasonId;
|
||||
|
||||
saveItemStatement.TryBind("@SeasonId", nullableSeasonId);
|
||||
}
|
||||
@@ -987,7 +979,7 @@ namespace Emby.Server.Implementations.Data
|
||||
|
||||
if (item is IHasSeries hasSeries)
|
||||
{
|
||||
var nullableSeriesId = hasSeries.SeriesId.Equals(Guid.Empty) ? (Guid?)null : hasSeries.SeriesId;
|
||||
var nullableSeriesId = hasSeries.SeriesId.Equals(default) ? (Guid?)null : hasSeries.SeriesId;
|
||||
|
||||
saveItemStatement.TryBind("@SeriesId", nullableSeriesId);
|
||||
saveItemStatement.TryBind("@SeriesPresentationUniqueKey", hasSeries.SeriesPresentationUniqueKey);
|
||||
@@ -1060,7 +1052,7 @@ namespace Emby.Server.Implementations.Data
|
||||
}
|
||||
|
||||
Guid ownerId = item.OwnerId;
|
||||
if (ownerId == Guid.Empty)
|
||||
if (ownerId.Equals(default))
|
||||
{
|
||||
saveItemStatement.TryBindNull("@OwnerId");
|
||||
}
|
||||
@@ -1198,13 +1190,15 @@ namespace Emby.Server.Implementations.Data
|
||||
bldr.Append(Delimiter)
|
||||
// Replace delimiters with other characters.
|
||||
// This can be removed when we migrate to a proper DB.
|
||||
.Append(hash.Replace('*', '/').Replace('|', '\\'));
|
||||
.Append(hash.Replace(Delimiter, '/').Replace('|', '\\'));
|
||||
}
|
||||
}
|
||||
|
||||
internal ItemImageInfo ItemImageInfoFromValueString(ReadOnlySpan<char> value)
|
||||
{
|
||||
var nextSegment = value.IndexOf('*');
|
||||
const char Delimiter = '*';
|
||||
|
||||
var nextSegment = value.IndexOf(Delimiter);
|
||||
if (nextSegment == -1)
|
||||
{
|
||||
return null;
|
||||
@@ -1212,7 +1206,7 @@ namespace Emby.Server.Implementations.Data
|
||||
|
||||
ReadOnlySpan<char> path = value[..nextSegment];
|
||||
value = value[(nextSegment + 1)..];
|
||||
nextSegment = value.IndexOf('*');
|
||||
nextSegment = value.IndexOf(Delimiter);
|
||||
if (nextSegment == -1)
|
||||
{
|
||||
return null;
|
||||
@@ -1220,7 +1214,7 @@ namespace Emby.Server.Implementations.Data
|
||||
|
||||
ReadOnlySpan<char> dateModified = value[..nextSegment];
|
||||
value = value[(nextSegment + 1)..];
|
||||
nextSegment = value.IndexOf('*');
|
||||
nextSegment = value.IndexOf(Delimiter);
|
||||
if (nextSegment == -1)
|
||||
{
|
||||
nextSegment = value.Length;
|
||||
@@ -1257,7 +1251,7 @@ namespace Emby.Server.Implementations.Data
|
||||
if (nextSegment + 1 < value.Length - 1)
|
||||
{
|
||||
value = value[(nextSegment + 1)..];
|
||||
nextSegment = value.IndexOf('*');
|
||||
nextSegment = value.IndexOf(Delimiter);
|
||||
if (nextSegment == -1 || nextSegment == value.Length)
|
||||
{
|
||||
return image;
|
||||
@@ -1266,7 +1260,7 @@ namespace Emby.Server.Implementations.Data
|
||||
ReadOnlySpan<char> widthSpan = value[..nextSegment];
|
||||
|
||||
value = value[(nextSegment + 1)..];
|
||||
nextSegment = value.IndexOf('*');
|
||||
nextSegment = value.IndexOf(Delimiter);
|
||||
if (nextSegment == -1)
|
||||
{
|
||||
nextSegment = value.Length;
|
||||
@@ -1292,7 +1286,7 @@ namespace Emby.Server.Implementations.Data
|
||||
var c = value[i];
|
||||
blurHashSpan[i] = c switch
|
||||
{
|
||||
'/' => '*',
|
||||
'/' => Delimiter,
|
||||
'\\' => '|',
|
||||
_ => c
|
||||
};
|
||||
@@ -1314,7 +1308,7 @@ namespace Emby.Server.Implementations.Data
|
||||
/// <exception cref="ArgumentException"><paramr name="id"/> is <seealso cref="Guid.Empty"/>.</exception>
|
||||
public BaseItem RetrieveItem(Guid id)
|
||||
{
|
||||
if (id == Guid.Empty)
|
||||
if (id.Equals(default))
|
||||
{
|
||||
throw new ArgumentException("Guid can't be empty", nameof(id));
|
||||
}
|
||||
@@ -2086,7 +2080,7 @@ namespace Emby.Server.Implementations.Data
|
||||
{
|
||||
CheckDisposed();
|
||||
|
||||
if (id.Equals(Guid.Empty))
|
||||
if (id.Equals(default))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(id));
|
||||
}
|
||||
@@ -2426,7 +2420,7 @@ namespace Emby.Server.Implementations.Data
|
||||
}
|
||||
|
||||
// genres, tags, studios, person, year?
|
||||
builder.Append("+ (Select count(1) * 10 from ItemValues where ItemId=Guid and CleanValue in (select CleanValue from itemvalues where ItemId=@SimilarItemId))");
|
||||
builder.Append("+ (Select count(1) * 10 from ItemValues where ItemId=Guid and CleanValue in (select CleanValue from ItemValues where ItemId=@SimilarItemId))");
|
||||
|
||||
if (item is MusicArtist)
|
||||
{
|
||||
@@ -2492,12 +2486,12 @@ namespace Emby.Server.Implementations.Data
|
||||
searchTerm = GetCleanValue(searchTerm);
|
||||
|
||||
var commandText = statement.SQL;
|
||||
if (commandText.IndexOf("@SearchTermStartsWith", StringComparison.OrdinalIgnoreCase) != -1)
|
||||
if (commandText.Contains("@SearchTermStartsWith", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
statement.TryBind("@SearchTermStartsWith", searchTerm + "%");
|
||||
}
|
||||
|
||||
if (commandText.IndexOf("@SearchTermContains", StringComparison.OrdinalIgnoreCase) != -1)
|
||||
if (commandText.Contains("@SearchTermContains", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
statement.TryBind("@SearchTermContains", "%" + searchTerm + "%");
|
||||
}
|
||||
@@ -2514,17 +2508,17 @@ namespace Emby.Server.Implementations.Data
|
||||
|
||||
var commandText = statement.SQL;
|
||||
|
||||
if (commandText.IndexOf("@ItemOfficialRating", StringComparison.OrdinalIgnoreCase) != -1)
|
||||
if (commandText.Contains("@ItemOfficialRating", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
statement.TryBind("@ItemOfficialRating", item.OfficialRating);
|
||||
}
|
||||
|
||||
if (commandText.IndexOf("@ItemProductionYear", StringComparison.OrdinalIgnoreCase) != -1)
|
||||
if (commandText.Contains("@ItemProductionYear", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
statement.TryBind("@ItemProductionYear", item.ProductionYear ?? 0);
|
||||
}
|
||||
|
||||
if (commandText.IndexOf("@SimilarItemId", StringComparison.OrdinalIgnoreCase) != -1)
|
||||
if (commandText.Contains("@SimilarItemId", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
statement.TryBind("@SimilarItemId", item.Id);
|
||||
}
|
||||
@@ -2758,12 +2752,12 @@ namespace Emby.Server.Implementations.Data
|
||||
|
||||
foreach (var providerId in newItem.ProviderIds)
|
||||
{
|
||||
if (providerId.Key == MetadataProvider.TmdbCollection.ToString())
|
||||
if (string.Equals(providerId.Key, nameof(MetadataProvider.TmdbCollection), StringComparison.Ordinal))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (item.GetProviderId(providerId.Key) == providerId.Value)
|
||||
if (string.Equals(item.GetProviderId(providerId.Key), providerId.Value, StringComparison.Ordinal))
|
||||
{
|
||||
if (newItem.SourceType == SourceType.Library)
|
||||
{
|
||||
@@ -2810,11 +2804,10 @@ namespace Emby.Server.Implementations.Data
|
||||
if (!query.EnableTotalRecordCount || (!query.Limit.HasValue && (query.StartIndex ?? 0) == 0))
|
||||
{
|
||||
var returnList = GetItemList(query);
|
||||
return new QueryResult<BaseItem>
|
||||
{
|
||||
Items = returnList,
|
||||
TotalRecordCount = returnList.Count
|
||||
};
|
||||
return new QueryResult<BaseItem>(
|
||||
query.StartIndex,
|
||||
returnList.Count,
|
||||
returnList);
|
||||
}
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
@@ -2978,6 +2971,7 @@ namespace Emby.Server.Implementations.Data
|
||||
ReadTransactionMode);
|
||||
}
|
||||
|
||||
result.StartIndex = query.StartIndex ?? 0;
|
||||
result.Items = list;
|
||||
return result;
|
||||
}
|
||||
@@ -3051,7 +3045,7 @@ namespace Emby.Server.Implementations.Data
|
||||
|
||||
if (string.Equals(name, ItemSortBy.PlayCount, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return "PlayCount";
|
||||
return ItemSortBy.PlayCount;
|
||||
}
|
||||
|
||||
if (string.Equals(name, ItemSortBy.IsFavoriteOrLiked, StringComparison.OrdinalIgnoreCase))
|
||||
@@ -3061,7 +3055,7 @@ namespace Emby.Server.Implementations.Data
|
||||
|
||||
if (string.Equals(name, ItemSortBy.IsFolder, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return "IsFolder";
|
||||
return ItemSortBy.IsFolder;
|
||||
}
|
||||
|
||||
if (string.Equals(name, ItemSortBy.IsPlayed, StringComparison.OrdinalIgnoreCase))
|
||||
@@ -3081,12 +3075,12 @@ namespace Emby.Server.Implementations.Data
|
||||
|
||||
if (string.Equals(name, ItemSortBy.Artist, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return "(select CleanValue from itemvalues where ItemId=Guid and Type=0 LIMIT 1)";
|
||||
return "(select CleanValue from ItemValues where ItemId=Guid and Type=0 LIMIT 1)";
|
||||
}
|
||||
|
||||
if (string.Equals(name, ItemSortBy.AlbumArtist, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return "(select CleanValue from itemvalues where ItemId=Guid and Type=1 LIMIT 1)";
|
||||
return "(select CleanValue from ItemValues where ItemId=Guid and Type=1 LIMIT 1)";
|
||||
}
|
||||
|
||||
if (string.Equals(name, ItemSortBy.OfficialRating, StringComparison.OrdinalIgnoreCase))
|
||||
@@ -3096,7 +3090,7 @@ namespace Emby.Server.Implementations.Data
|
||||
|
||||
if (string.Equals(name, ItemSortBy.Studio, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return "(select CleanValue from itemvalues where ItemId=Guid and Type=3 LIMIT 1)";
|
||||
return "(select CleanValue from ItemValues where ItemId=Guid and Type=3 LIMIT 1)";
|
||||
}
|
||||
|
||||
if (string.Equals(name, ItemSortBy.SeriesDatePlayed, StringComparison.OrdinalIgnoreCase))
|
||||
@@ -3109,7 +3103,73 @@ namespace Emby.Server.Implementations.Data
|
||||
return "SeriesName";
|
||||
}
|
||||
|
||||
return name;
|
||||
if (string.Equals(name, ItemSortBy.AiredEpisodeOrder, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return ItemSortBy.AiredEpisodeOrder;
|
||||
}
|
||||
|
||||
if (string.Equals(name, ItemSortBy.Album, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return ItemSortBy.Album;
|
||||
}
|
||||
|
||||
if (string.Equals(name, ItemSortBy.DateCreated, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return ItemSortBy.DateCreated;
|
||||
}
|
||||
|
||||
if (string.Equals(name, ItemSortBy.PremiereDate, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return ItemSortBy.PremiereDate;
|
||||
}
|
||||
|
||||
if (string.Equals(name, ItemSortBy.StartDate, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return ItemSortBy.StartDate;
|
||||
}
|
||||
|
||||
if (string.Equals(name, ItemSortBy.Name, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return ItemSortBy.Name;
|
||||
}
|
||||
|
||||
if (string.Equals(name, ItemSortBy.CommunityRating, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return ItemSortBy.CommunityRating;
|
||||
}
|
||||
|
||||
if (string.Equals(name, ItemSortBy.ProductionYear, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return ItemSortBy.ProductionYear;
|
||||
}
|
||||
|
||||
if (string.Equals(name, ItemSortBy.CriticRating, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return ItemSortBy.CriticRating;
|
||||
}
|
||||
|
||||
if (string.Equals(name, ItemSortBy.VideoBitRate, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return ItemSortBy.VideoBitRate;
|
||||
}
|
||||
|
||||
if (string.Equals(name, ItemSortBy.ParentIndexNumber, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return ItemSortBy.ParentIndexNumber;
|
||||
}
|
||||
|
||||
if (string.Equals(name, ItemSortBy.IndexNumber, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return ItemSortBy.IndexNumber;
|
||||
}
|
||||
|
||||
if (string.Equals(name, ItemSortBy.SimilarityScore, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return ItemSortBy.SimilarityScore;
|
||||
}
|
||||
|
||||
// Unknown SortBy, just sort by the SortName.
|
||||
return ItemSortBy.SortName;
|
||||
}
|
||||
|
||||
public List<Guid> GetItemIdsList(InternalItemsQuery query)
|
||||
@@ -3185,220 +3245,6 @@ namespace Emby.Server.Implementations.Data
|
||||
return list;
|
||||
}
|
||||
|
||||
public List<Tuple<Guid, string>> GetItemIdsWithPath(InternalItemsQuery query)
|
||||
{
|
||||
if (query == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(query));
|
||||
}
|
||||
|
||||
CheckDisposed();
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
var columns = new List<string> { "guid", "path" };
|
||||
SetFinalColumnsToSelect(query, columns);
|
||||
var commandText = "select " + string.Join(',', columns) + FromText;
|
||||
|
||||
var whereClauses = GetWhereClauses(query, null);
|
||||
if (whereClauses.Count != 0)
|
||||
{
|
||||
commandText += " where " + string.Join(" AND ", whereClauses);
|
||||
}
|
||||
|
||||
commandText += GetGroupBy(query)
|
||||
+ GetOrderByText(query);
|
||||
|
||||
if (query.Limit.HasValue || query.StartIndex.HasValue)
|
||||
{
|
||||
var offset = query.StartIndex ?? 0;
|
||||
|
||||
if (query.Limit.HasValue || offset > 0)
|
||||
{
|
||||
commandText += " LIMIT " + (query.Limit ?? int.MaxValue).ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
if (offset > 0)
|
||||
{
|
||||
commandText += " OFFSET " + offset.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
|
||||
var list = new List<Tuple<Guid, string>>();
|
||||
using (var connection = GetConnection(true))
|
||||
{
|
||||
using (var statement = PrepareStatement(connection, commandText))
|
||||
{
|
||||
if (EnableJoinUserData(query))
|
||||
{
|
||||
statement.TryBind("@UserId", query.User.InternalId);
|
||||
}
|
||||
|
||||
// Running this again will bind the params
|
||||
GetWhereClauses(query, statement);
|
||||
|
||||
foreach (var row in statement.ExecuteQuery())
|
||||
{
|
||||
var id = row.GetGuid(0);
|
||||
|
||||
row.TryGetString(1, out var path);
|
||||
|
||||
list.Add(new Tuple<Guid, string>(id, path));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LogQueryTime("GetItemIdsWithPath", commandText, now);
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
public QueryResult<Guid> GetItemIds(InternalItemsQuery query)
|
||||
{
|
||||
if (query == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(query));
|
||||
}
|
||||
|
||||
CheckDisposed();
|
||||
|
||||
if (!query.EnableTotalRecordCount || (!query.Limit.HasValue && (query.StartIndex ?? 0) == 0))
|
||||
{
|
||||
var returnList = GetItemIdsList(query);
|
||||
return new QueryResult<Guid>
|
||||
{
|
||||
Items = returnList,
|
||||
TotalRecordCount = returnList.Count
|
||||
};
|
||||
}
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
var columns = new List<string> { "guid" };
|
||||
SetFinalColumnsToSelect(query, columns);
|
||||
var commandText = "select "
|
||||
+ string.Join(',', columns)
|
||||
+ FromText
|
||||
+ GetJoinUserDataText(query);
|
||||
|
||||
var whereClauses = GetWhereClauses(query, null);
|
||||
|
||||
var whereText = whereClauses.Count == 0 ?
|
||||
string.Empty :
|
||||
" where " + string.Join(" AND ", whereClauses);
|
||||
|
||||
commandText += whereText
|
||||
+ GetGroupBy(query)
|
||||
+ GetOrderByText(query);
|
||||
|
||||
if (query.Limit.HasValue || query.StartIndex.HasValue)
|
||||
{
|
||||
var offset = query.StartIndex ?? 0;
|
||||
|
||||
if (query.Limit.HasValue || offset > 0)
|
||||
{
|
||||
commandText += " LIMIT " + (query.Limit ?? int.MaxValue).ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
if (offset > 0)
|
||||
{
|
||||
commandText += " OFFSET " + offset.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
|
||||
var isReturningZeroItems = query.Limit.HasValue && query.Limit <= 0;
|
||||
|
||||
var statementTexts = new List<string>();
|
||||
if (!isReturningZeroItems)
|
||||
{
|
||||
statementTexts.Add(commandText);
|
||||
}
|
||||
|
||||
if (query.EnableTotalRecordCount)
|
||||
{
|
||||
commandText = string.Empty;
|
||||
|
||||
List<string> columnsToSelect;
|
||||
if (EnableGroupByPresentationUniqueKey(query))
|
||||
{
|
||||
columnsToSelect = new List<string> { "count (distinct PresentationUniqueKey)" };
|
||||
}
|
||||
else if (query.GroupBySeriesPresentationUniqueKey)
|
||||
{
|
||||
columnsToSelect = new List<string> { "count (distinct SeriesPresentationUniqueKey)" };
|
||||
}
|
||||
else
|
||||
{
|
||||
columnsToSelect = new List<string> { "count (guid)" };
|
||||
}
|
||||
|
||||
SetFinalColumnsToSelect(query, columnsToSelect);
|
||||
commandText += " select " + string.Join(',', columnsToSelect) + FromText;
|
||||
|
||||
commandText += GetJoinUserDataText(query)
|
||||
+ whereText;
|
||||
statementTexts.Add(commandText);
|
||||
}
|
||||
|
||||
var list = new List<Guid>();
|
||||
var result = new QueryResult<Guid>();
|
||||
using (var connection = GetConnection(true))
|
||||
{
|
||||
connection.RunInTransaction(
|
||||
db =>
|
||||
{
|
||||
var statements = PrepareAll(db, statementTexts);
|
||||
|
||||
if (!isReturningZeroItems)
|
||||
{
|
||||
using (var statement = statements[0])
|
||||
{
|
||||
if (EnableJoinUserData(query))
|
||||
{
|
||||
statement.TryBind("@UserId", query.User.InternalId);
|
||||
}
|
||||
|
||||
BindSimilarParams(query, statement);
|
||||
BindSearchParams(query, statement);
|
||||
|
||||
// Running this again will bind the params
|
||||
GetWhereClauses(query, statement);
|
||||
|
||||
foreach (var row in statement.ExecuteQuery())
|
||||
{
|
||||
list.Add(row[0].ReadGuidFromBlob());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (query.EnableTotalRecordCount)
|
||||
{
|
||||
using (var statement = statements[statements.Length - 1])
|
||||
{
|
||||
if (EnableJoinUserData(query))
|
||||
{
|
||||
statement.TryBind("@UserId", query.User.InternalId);
|
||||
}
|
||||
|
||||
BindSimilarParams(query, statement);
|
||||
BindSearchParams(query, statement);
|
||||
|
||||
// Running this again will bind the params
|
||||
GetWhereClauses(query, statement);
|
||||
|
||||
result.TotalRecordCount = statement.ExecuteQuery().SelectScalarInt().First();
|
||||
}
|
||||
}
|
||||
},
|
||||
ReadTransactionMode);
|
||||
}
|
||||
|
||||
LogQueryTime("GetItemIds", commandText, now);
|
||||
|
||||
result.Items = list;
|
||||
return result;
|
||||
}
|
||||
|
||||
private bool IsAlphaNumeric(string str)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(str))
|
||||
@@ -3665,7 +3511,7 @@ namespace Emby.Server.Implementations.Data
|
||||
whereClauses.Add($"ChannelId in ({inClause})");
|
||||
}
|
||||
|
||||
if (!query.ParentId.Equals(Guid.Empty))
|
||||
if (!query.ParentId.Equals(default))
|
||||
{
|
||||
whereClauses.Add("ParentId=@ParentId");
|
||||
statement?.TryBind("@ParentId", query.ParentId);
|
||||
@@ -3695,6 +3541,13 @@ namespace Emby.Server.Implementations.Data
|
||||
statement?.TryBind("@MinIndexNumber", query.MinIndexNumber.Value);
|
||||
}
|
||||
|
||||
if (query.MinParentAndIndexNumber.HasValue)
|
||||
{
|
||||
whereClauses.Add("((ParentIndexNumber=@MinParentAndIndexNumberParent and IndexNumber>=@MinParentAndIndexNumberIndex) or ParentIndexNumber>@MinParentAndIndexNumberParent)");
|
||||
statement?.TryBind("@MinParentAndIndexNumberParent", query.MinParentAndIndexNumber.Value.ParentIndexNumber);
|
||||
statement?.TryBind("@MinParentAndIndexNumberIndex", query.MinParentAndIndexNumber.Value.IndexNumber);
|
||||
}
|
||||
|
||||
if (query.MinDateCreated.HasValue)
|
||||
{
|
||||
whereClauses.Add("DateCreated>=@MinDateCreated");
|
||||
@@ -4022,10 +3875,10 @@ namespace Emby.Server.Implementations.Data
|
||||
{
|
||||
var paramName = "@ArtistIds" + index;
|
||||
|
||||
clauses.Add("(guid in (select itemid from itemvalues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type<=1))");
|
||||
clauses.Add("(guid in (select itemid from ItemValues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type<=1))");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind(paramName, artistId.ToByteArray());
|
||||
statement.TryBind(paramName, artistId);
|
||||
}
|
||||
|
||||
index++;
|
||||
@@ -4043,10 +3896,10 @@ namespace Emby.Server.Implementations.Data
|
||||
{
|
||||
var paramName = "@ArtistIds" + index;
|
||||
|
||||
clauses.Add("(guid in (select itemid from itemvalues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type=1))");
|
||||
clauses.Add("(guid in (select itemid from ItemValues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type=1))");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind(paramName, artistId.ToByteArray());
|
||||
statement.TryBind(paramName, artistId);
|
||||
}
|
||||
|
||||
index++;
|
||||
@@ -4064,10 +3917,10 @@ namespace Emby.Server.Implementations.Data
|
||||
{
|
||||
var paramName = "@ArtistIds" + index;
|
||||
|
||||
clauses.Add("((select CleanName from TypedBaseItems where guid=" + paramName + ") in (select CleanValue from itemvalues where ItemId=Guid and Type=0) AND (select CleanName from TypedBaseItems where guid=" + paramName + ") not in (select CleanValue from itemvalues where ItemId=Guid and Type=1))");
|
||||
clauses.Add("((select CleanName from TypedBaseItems where guid=" + paramName + ") in (select CleanValue from ItemValues where ItemId=Guid and Type=0) AND (select CleanName from TypedBaseItems where guid=" + paramName + ") not in (select CleanValue from ItemValues where ItemId=Guid and Type=1))");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind(paramName, artistId.ToByteArray());
|
||||
statement.TryBind(paramName, artistId);
|
||||
}
|
||||
|
||||
index++;
|
||||
@@ -4088,7 +3941,7 @@ namespace Emby.Server.Implementations.Data
|
||||
clauses.Add("Album in (select Name from typedbaseitems where guid=" + paramName + ")");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind(paramName, albumId.ToByteArray());
|
||||
statement.TryBind(paramName, albumId);
|
||||
}
|
||||
|
||||
index++;
|
||||
@@ -4106,10 +3959,10 @@ namespace Emby.Server.Implementations.Data
|
||||
{
|
||||
var paramName = "@ExcludeArtistId" + index;
|
||||
|
||||
clauses.Add("(guid not in (select itemid from itemvalues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type<=1))");
|
||||
clauses.Add("(guid not in (select itemid from ItemValues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type<=1))");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind(paramName, artistId.ToByteArray());
|
||||
statement.TryBind(paramName, artistId);
|
||||
}
|
||||
|
||||
index++;
|
||||
@@ -4127,10 +3980,10 @@ namespace Emby.Server.Implementations.Data
|
||||
{
|
||||
var paramName = "@GenreId" + index;
|
||||
|
||||
clauses.Add("(guid in (select itemid from itemvalues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type=2))");
|
||||
clauses.Add("(guid in (select itemid from ItemValues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type=2))");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind(paramName, genreId.ToByteArray());
|
||||
statement.TryBind(paramName, genreId);
|
||||
}
|
||||
|
||||
index++;
|
||||
@@ -4146,7 +3999,7 @@ namespace Emby.Server.Implementations.Data
|
||||
var index = 0;
|
||||
foreach (var item in query.Genres)
|
||||
{
|
||||
clauses.Add("@Genre" + index + " in (select CleanValue from itemvalues where ItemId=Guid and Type=2)");
|
||||
clauses.Add("@Genre" + index + " in (select CleanValue from ItemValues where ItemId=Guid and Type=2)");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@Genre" + index, GetCleanValue(item));
|
||||
@@ -4165,7 +4018,7 @@ namespace Emby.Server.Implementations.Data
|
||||
var index = 0;
|
||||
foreach (var item in tags)
|
||||
{
|
||||
clauses.Add("@Tag" + index + " in (select CleanValue from itemvalues where ItemId=Guid and Type=4)");
|
||||
clauses.Add("@Tag" + index + " in (select CleanValue from ItemValues where ItemId=Guid and Type=4)");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@Tag" + index, GetCleanValue(item));
|
||||
@@ -4184,7 +4037,7 @@ namespace Emby.Server.Implementations.Data
|
||||
var index = 0;
|
||||
foreach (var item in excludeTags)
|
||||
{
|
||||
clauses.Add("@ExcludeTag" + index + " not in (select CleanValue from itemvalues where ItemId=Guid and Type=4)");
|
||||
clauses.Add("@ExcludeTag" + index + " not in (select CleanValue from ItemValues where ItemId=Guid and Type=4)");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@ExcludeTag" + index, GetCleanValue(item));
|
||||
@@ -4205,11 +4058,11 @@ namespace Emby.Server.Implementations.Data
|
||||
{
|
||||
var paramName = "@StudioId" + index;
|
||||
|
||||
clauses.Add("(guid in (select itemid from itemvalues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type=3))");
|
||||
clauses.Add("(guid in (select itemid from ItemValues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type=3))");
|
||||
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind(paramName, studioId.ToByteArray());
|
||||
statement.TryBind(paramName, studioId);
|
||||
}
|
||||
|
||||
index++;
|
||||
@@ -4494,7 +4347,7 @@ namespace Emby.Server.Implementations.Data
|
||||
var index = 0;
|
||||
foreach (var pair in query.ExcludeProviderIds)
|
||||
{
|
||||
if (string.Equals(pair.Key, MetadataProvider.TmdbCollection.ToString(), StringComparison.OrdinalIgnoreCase))
|
||||
if (string.Equals(pair.Key, nameof(MetadataProvider.TmdbCollection), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -4524,7 +4377,7 @@ namespace Emby.Server.Implementations.Data
|
||||
var index = 0;
|
||||
foreach (var pair in query.HasAnyProviderId)
|
||||
{
|
||||
if (string.Equals(pair.Key, MetadataProvider.TmdbCollection.ToString(), StringComparison.OrdinalIgnoreCase))
|
||||
if (string.Equals(pair.Key, nameof(MetadataProvider.TmdbCollection), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -4684,7 +4537,7 @@ namespace Emby.Server.Implementations.Data
|
||||
{
|
||||
int index = 0;
|
||||
string excludedTags = string.Join(',', query.ExcludeInheritedTags.Select(_ => paramName + index++));
|
||||
whereClauses.Add("((select CleanValue from itemvalues where ItemId=Guid and Type=6 and cleanvalue in (" + excludedTags + ")) is null)");
|
||||
whereClauses.Add("((select CleanValue from ItemValues where ItemId=Guid and Type=6 and cleanvalue in (" + excludedTags + ")) is null)");
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -4919,11 +4772,11 @@ namespace Emby.Server.Implementations.Data
|
||||
';',
|
||||
new string[]
|
||||
{
|
||||
"delete from itemvalues where type = 6",
|
||||
"delete from ItemValues where type = 6",
|
||||
|
||||
"insert into itemvalues (ItemId, Type, Value, CleanValue) select ItemId, 6, Value, CleanValue from ItemValues where Type=4",
|
||||
"insert into ItemValues (ItemId, Type, Value, CleanValue) select ItemId, 6, Value, CleanValue from ItemValues where Type=4",
|
||||
|
||||
@"insert into itemvalues (ItemId, Type, Value, CleanValue) select AncestorIds.itemid, 6, ItemValues.Value, ItemValues.CleanValue
|
||||
@"insert into ItemValues (ItemId, Type, Value, CleanValue) select AncestorIds.itemid, 6, ItemValues.Value, ItemValues.CleanValue
|
||||
FROM AncestorIds
|
||||
LEFT JOIN ItemValues ON (AncestorIds.AncestorId = ItemValues.ItemId)
|
||||
where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type = 4 "
|
||||
@@ -4942,7 +4795,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||
|
||||
public void DeleteItem(Guid id)
|
||||
{
|
||||
if (id == Guid.Empty)
|
||||
if (id.Equals(default))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(id));
|
||||
}
|
||||
@@ -4954,7 +4807,8 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||
connection.RunInTransaction(
|
||||
db =>
|
||||
{
|
||||
var idBlob = id.ToByteArray();
|
||||
Span<byte> idBlob = stackalloc byte[16];
|
||||
id.TryWriteBytes(idBlob);
|
||||
|
||||
// Delete people
|
||||
ExecuteWithSingleParam(db, "delete from People where ItemId=@Id", idBlob);
|
||||
@@ -5003,7 +4857,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||
|
||||
if (whereClauses.Count != 0)
|
||||
{
|
||||
commandText.Append(" where ").Append(string.Join(" AND ", whereClauses));
|
||||
commandText.Append(" where ").AppendJoin(" AND ", whereClauses);
|
||||
}
|
||||
|
||||
commandText.Append(" order by ListOrder");
|
||||
@@ -5087,18 +4941,19 @@ SELECT key FROM UserDatas WHERE isFavorite=@IsFavorite AND userId=@UserId)
|
||||
AND Type = @InternalPersonType)");
|
||||
statement?.TryBind("@IsFavorite", query.IsFavorite.Value);
|
||||
statement?.TryBind("@InternalPersonType", typeof(Person).FullName);
|
||||
statement?.TryBind("@UserId", query.User.InternalId);
|
||||
}
|
||||
|
||||
if (!query.ItemId.Equals(Guid.Empty))
|
||||
if (!query.ItemId.Equals(default))
|
||||
{
|
||||
whereClauses.Add("ItemId=@ItemId");
|
||||
statement?.TryBind("@ItemId", query.ItemId.ToByteArray());
|
||||
statement?.TryBind("@ItemId", query.ItemId);
|
||||
}
|
||||
|
||||
if (!query.AppearsInItemId.Equals(Guid.Empty))
|
||||
if (!query.AppearsInItemId.Equals(default))
|
||||
{
|
||||
whereClauses.Add("p.Name in (Select Name from People where ItemId=@AppearsInItemId)");
|
||||
statement?.TryBind("@AppearsInItemId", query.AppearsInItemId.ToByteArray());
|
||||
statement?.TryBind("@AppearsInItemId", query.AppearsInItemId);
|
||||
}
|
||||
|
||||
var queryPersonTypes = query.PersonTypes.Where(IsValidPersonType).ToList();
|
||||
@@ -5141,17 +4996,12 @@ AND Type = @InternalPersonType)");
|
||||
statement?.TryBind("@NameContains", "%" + query.NameContains + "%");
|
||||
}
|
||||
|
||||
if (query.User != null)
|
||||
{
|
||||
statement?.TryBind("@UserId", query.User.InternalId);
|
||||
}
|
||||
|
||||
return whereClauses;
|
||||
}
|
||||
|
||||
private void UpdateAncestors(Guid itemId, List<Guid> ancestorIds, IDatabaseConnection db, IStatement deleteAncestorsStatement)
|
||||
{
|
||||
if (itemId.Equals(Guid.Empty))
|
||||
if (itemId.Equals(default))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(itemId));
|
||||
}
|
||||
@@ -5599,6 +5449,7 @@ AND Type = @InternalPersonType)");
|
||||
result.TotalRecordCount = list.Count;
|
||||
}
|
||||
|
||||
result.StartIndex = query.StartIndex ?? 0;
|
||||
result.Items = list;
|
||||
|
||||
return result;
|
||||
@@ -5682,7 +5533,7 @@ AND Type = @InternalPersonType)");
|
||||
|
||||
private void UpdateItemValues(Guid itemId, List<(int MagicNumber, string Value)> values, IDatabaseConnection db)
|
||||
{
|
||||
if (itemId.Equals(Guid.Empty))
|
||||
if (itemId.Equals(default))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(itemId));
|
||||
}
|
||||
@@ -5758,7 +5609,7 @@ AND Type = @InternalPersonType)");
|
||||
|
||||
public void UpdatePeople(Guid itemId, List<PersonInfo> people)
|
||||
{
|
||||
if (itemId.Equals(Guid.Empty))
|
||||
if (itemId.Equals(default))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(itemId));
|
||||
}
|
||||
@@ -5891,7 +5742,7 @@ AND Type = @InternalPersonType)");
|
||||
|
||||
using (var statement = PrepareStatement(connection, cmdText))
|
||||
{
|
||||
statement.TryBind("@ItemId", query.ItemId.ToByteArray());
|
||||
statement.TryBind("@ItemId", query.ItemId);
|
||||
|
||||
if (query.Type.HasValue)
|
||||
{
|
||||
@@ -5913,11 +5764,11 @@ AND Type = @InternalPersonType)");
|
||||
}
|
||||
}
|
||||
|
||||
public void SaveMediaStreams(Guid id, List<MediaStream> streams, CancellationToken cancellationToken)
|
||||
public void SaveMediaStreams(Guid id, IReadOnlyList<MediaStream> streams, CancellationToken cancellationToken)
|
||||
{
|
||||
CheckDisposed();
|
||||
|
||||
if (id == Guid.Empty)
|
||||
if (id.Equals(default))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(id));
|
||||
}
|
||||
@@ -5936,7 +5787,7 @@ AND Type = @InternalPersonType)");
|
||||
{
|
||||
var itemIdBlob = id.ToByteArray();
|
||||
|
||||
// First delete chapters
|
||||
// Delete existing mediastreams
|
||||
db.Execute("delete from mediastreams where ItemId=@ItemId", itemIdBlob);
|
||||
|
||||
InsertMediaStreams(itemIdBlob, streams, db);
|
||||
@@ -5945,7 +5796,7 @@ AND Type = @InternalPersonType)");
|
||||
}
|
||||
}
|
||||
|
||||
private void InsertMediaStreams(byte[] idBlob, List<MediaStream> streams, IDatabaseConnection db)
|
||||
private void InsertMediaStreams(byte[] idBlob, IReadOnlyList<MediaStream> streams, IDatabaseConnection db)
|
||||
{
|
||||
const int Limit = 10;
|
||||
var startIndex = 0;
|
||||
@@ -6028,6 +5879,15 @@ AND Type = @InternalPersonType)");
|
||||
statement.TryBind("@ColorPrimaries" + index, stream.ColorPrimaries);
|
||||
statement.TryBind("@ColorSpace" + index, stream.ColorSpace);
|
||||
statement.TryBind("@ColorTransfer" + index, stream.ColorTransfer);
|
||||
|
||||
statement.TryBind("@DvVersionMajor" + index, stream.DvVersionMajor);
|
||||
statement.TryBind("@DvVersionMinor" + index, stream.DvVersionMinor);
|
||||
statement.TryBind("@DvProfile" + index, stream.DvProfile);
|
||||
statement.TryBind("@DvLevel" + index, stream.DvLevel);
|
||||
statement.TryBind("@RpuPresentFlag" + index, stream.RpuPresentFlag);
|
||||
statement.TryBind("@ElPresentFlag" + index, stream.ElPresentFlag);
|
||||
statement.TryBind("@BlPresentFlag" + index, stream.BlPresentFlag);
|
||||
statement.TryBind("@DvBlSignalCompatibilityId" + index, stream.DvBlSignalCompatibilityId);
|
||||
}
|
||||
|
||||
statement.Reset();
|
||||
@@ -6040,10 +5900,10 @@ AND Type = @InternalPersonType)");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the chapter.
|
||||
/// Gets the media stream.
|
||||
/// </summary>
|
||||
/// <param name="reader">The reader.</param>
|
||||
/// <returns>ChapterInfo.</returns>
|
||||
/// <returns>MediaStream.</returns>
|
||||
private MediaStream GetMediaStream(IReadOnlyList<ResultSetValue> reader)
|
||||
{
|
||||
var item = new MediaStream
|
||||
@@ -6199,11 +6059,52 @@ AND Type = @InternalPersonType)");
|
||||
item.ColorTransfer = colorTransfer;
|
||||
}
|
||||
|
||||
if (reader.TryGetInt32(35, out var dvVersionMajor))
|
||||
{
|
||||
item.DvVersionMajor = dvVersionMajor;
|
||||
}
|
||||
|
||||
if (reader.TryGetInt32(36, out var dvVersionMinor))
|
||||
{
|
||||
item.DvVersionMinor = dvVersionMinor;
|
||||
}
|
||||
|
||||
if (reader.TryGetInt32(37, out var dvProfile))
|
||||
{
|
||||
item.DvProfile = dvProfile;
|
||||
}
|
||||
|
||||
if (reader.TryGetInt32(38, out var dvLevel))
|
||||
{
|
||||
item.DvLevel = dvLevel;
|
||||
}
|
||||
|
||||
if (reader.TryGetInt32(39, out var rpuPresentFlag))
|
||||
{
|
||||
item.RpuPresentFlag = rpuPresentFlag;
|
||||
}
|
||||
|
||||
if (reader.TryGetInt32(40, out var elPresentFlag))
|
||||
{
|
||||
item.ElPresentFlag = elPresentFlag;
|
||||
}
|
||||
|
||||
if (reader.TryGetInt32(41, out var blPresentFlag))
|
||||
{
|
||||
item.BlPresentFlag = blPresentFlag;
|
||||
}
|
||||
|
||||
if (reader.TryGetInt32(42, out var dvBlSignalCompatibilityId))
|
||||
{
|
||||
item.DvBlSignalCompatibilityId = dvBlSignalCompatibilityId;
|
||||
}
|
||||
|
||||
if (item.Type == MediaStreamType.Subtitle)
|
||||
{
|
||||
item.LocalizedUndefined = _localization.GetLocalizedString("Undefined");
|
||||
item.LocalizedDefault = _localization.GetLocalizedString("Default");
|
||||
item.LocalizedForced = _localization.GetLocalizedString("Forced");
|
||||
item.LocalizedExternal = _localization.GetLocalizedString("External");
|
||||
}
|
||||
|
||||
return item;
|
||||
@@ -6253,7 +6154,7 @@ AND Type = @InternalPersonType)");
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
CheckDisposed();
|
||||
if (id == Guid.Empty)
|
||||
if (id.Equals(default))
|
||||
{
|
||||
throw new ArgumentException("Guid can't be empty.", nameof(id));
|
||||
}
|
||||
|
||||
@@ -26,9 +26,6 @@ namespace Emby.Server.Implementations.Data
|
||||
DbFilePath = Path.Combine(appPaths.DataPath, "library.db");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Name => "SQLite";
|
||||
|
||||
/// <summary>
|
||||
/// Opens the connection to the database.
|
||||
/// </summary>
|
||||
@@ -102,7 +99,7 @@ namespace Emby.Server.Implementations.Data
|
||||
continue;
|
||||
}
|
||||
|
||||
statement.TryBind("@UserId", user.Id.ToByteArray());
|
||||
statement.TryBind("@UserId", user.Id);
|
||||
statement.TryBind("@InternalUserId", user.InternalId);
|
||||
|
||||
statement.MoveNext();
|
||||
@@ -390,6 +387,7 @@ namespace Emby.Server.Implementations.Data
|
||||
return userData;
|
||||
}
|
||||
|
||||
#pragma warning disable CA2215
|
||||
/// <inheritdoc/>
|
||||
/// <remarks>
|
||||
/// There is nothing to dispose here since <see cref="BaseSqliteRepository.WriteLock"/> and
|
||||
@@ -398,6 +396,10 @@ namespace Emby.Server.Implementations.Data
|
||||
/// </remarks>
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Controller.Persistence;
|
||||
using MediaBrowser.Controller.Playlists;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Drawing;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Querying;
|
||||
@@ -183,7 +182,7 @@ namespace Emby.Server.Implementations.Dto
|
||||
|
||||
if (options.ContainsField(ItemFields.People))
|
||||
{
|
||||
AttachPeople(dto, item);
|
||||
AttachPeople(dto, item, user);
|
||||
}
|
||||
|
||||
if (options.ContainsField(ItemFields.PrimaryImageAspectRatio))
|
||||
@@ -458,11 +457,6 @@ namespace Emby.Server.Implementations.Dto
|
||||
}
|
||||
}
|
||||
|
||||
private string GetDtoId(BaseItem item)
|
||||
{
|
||||
return item.Id.ToString("N", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
private void SetMusicVideoProperties(BaseItemDto dto, MusicVideo item)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(item.Album))
|
||||
@@ -509,7 +503,8 @@ namespace Emby.Server.Implementations.Dto
|
||||
/// </summary>
|
||||
/// <param name="dto">The dto.</param>
|
||||
/// <param name="item">The item.</param>
|
||||
private void AttachPeople(BaseItemDto dto, BaseItem item)
|
||||
/// <param name="user">The requesting user.</param>
|
||||
private void AttachPeople(BaseItemDto dto, BaseItem item, User user = null)
|
||||
{
|
||||
// Ordering by person type to ensure actors and artists are at the front.
|
||||
// This is taking advantage of the fact that they both begin with A
|
||||
@@ -566,6 +561,9 @@ namespace Emby.Server.Implementations.Dto
|
||||
return null;
|
||||
}
|
||||
}).Where(i => i != null)
|
||||
.Where(i => user == null ?
|
||||
true :
|
||||
i.IsVisible(user))
|
||||
.GroupBy(i => i.Name, StringComparer.OrdinalIgnoreCase)
|
||||
.Select(x => x.First())
|
||||
.ToDictionary(i => i.Name, StringComparer.OrdinalIgnoreCase);
|
||||
@@ -584,7 +582,7 @@ namespace Emby.Server.Implementations.Dto
|
||||
if (dictionary.TryGetValue(person.Name, out Person entity))
|
||||
{
|
||||
baseItemPerson.PrimaryImageTag = GetTagAndFillBlurhash(dto, entity, ImageType.Primary);
|
||||
baseItemPerson.Id = entity.Id.ToString("N", CultureInfo.InvariantCulture);
|
||||
baseItemPerson.Id = entity.Id;
|
||||
if (dto.ImageBlurHashes != null)
|
||||
{
|
||||
// Only add BlurHash for the person's image.
|
||||
@@ -743,8 +741,7 @@ namespace Emby.Server.Implementations.Dto
|
||||
dto.Tags = item.Tags;
|
||||
}
|
||||
|
||||
var hasAspectRatio = item as IHasAspectRatio;
|
||||
if (hasAspectRatio != null)
|
||||
if (item is IHasAspectRatio hasAspectRatio)
|
||||
{
|
||||
dto.AspectRatio = hasAspectRatio.AspectRatio;
|
||||
}
|
||||
@@ -894,15 +891,13 @@ namespace Emby.Server.Implementations.Dto
|
||||
dto.CommunityRating = item.CommunityRating;
|
||||
}
|
||||
|
||||
var supportsPlaceHolders = item as ISupportsPlaceHolders;
|
||||
if (supportsPlaceHolders != null && supportsPlaceHolders.IsPlaceHolder)
|
||||
if (item is ISupportsPlaceHolders supportsPlaceHolders && supportsPlaceHolders.IsPlaceHolder)
|
||||
{
|
||||
dto.IsPlaceHolder = supportsPlaceHolders.IsPlaceHolder;
|
||||
}
|
||||
|
||||
// Add audio info
|
||||
var audio = item as Audio;
|
||||
if (audio != null)
|
||||
if (item is Audio audio)
|
||||
{
|
||||
dto.Album = audio.Album;
|
||||
if (audio.ExtraType.HasValue)
|
||||
@@ -975,8 +970,7 @@ namespace Emby.Server.Implementations.Dto
|
||||
}).Where(i => i != null).ToArray();
|
||||
}
|
||||
|
||||
var hasAlbumArtist = item as IHasAlbumArtist;
|
||||
if (hasAlbumArtist != null)
|
||||
if (item is IHasAlbumArtist hasAlbumArtist)
|
||||
{
|
||||
dto.AlbumArtist = hasAlbumArtist.AlbumArtists.FirstOrDefault();
|
||||
|
||||
@@ -1102,12 +1096,13 @@ namespace Emby.Server.Implementations.Dto
|
||||
|
||||
if (options.ContainsField(ItemFields.LocalTrailerCount))
|
||||
{
|
||||
allExtras ??= item.GetExtras().ToArray();
|
||||
dto.LocalTrailerCount = allExtras.Count(i => i.ExtraType == ExtraType.Trailer);
|
||||
|
||||
if (item is IHasTrailers hasTrailers)
|
||||
{
|
||||
dto.LocalTrailerCount += hasTrailers.GetTrailerCount();
|
||||
dto.LocalTrailerCount = hasTrailers.LocalTrailers.Count;
|
||||
}
|
||||
else
|
||||
{
|
||||
dto.LocalTrailerCount = (allExtras ?? item.GetExtras()).Count(i => i.ExtraType == ExtraType.Trailer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1317,35 +1312,35 @@ namespace Emby.Server.Implementations.Dto
|
||||
|
||||
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);
|
||||
|
||||
if (image != null)
|
||||
{
|
||||
dto.ParentLogoItemId = GetDtoId(parent);
|
||||
dto.ParentLogoItemId = parent.Id;
|
||||
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);
|
||||
|
||||
if (image != null)
|
||||
{
|
||||
dto.ParentArtItemId = GetDtoId(parent);
|
||||
dto.ParentArtItemId = parent.Id;
|
||||
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);
|
||||
|
||||
if (image != null)
|
||||
{
|
||||
dto.ParentThumbItemId = GetDtoId(parent);
|
||||
dto.ParentThumbItemId = parent.Id;
|
||||
dto.ParentThumbImageTag = GetTagAndFillBlurhash(dto, parent, image);
|
||||
}
|
||||
}
|
||||
@@ -1356,7 +1351,7 @@ namespace Emby.Server.Implementations.Dto
|
||||
|
||||
if (images.Count > 0)
|
||||
{
|
||||
dto.ParentBackdropItemId = GetDtoId(parent);
|
||||
dto.ParentBackdropItemId = parent.Id;
|
||||
dto.ParentBackdropImageTags = GetTagsAndFillBlurhashes(dto, parent, ImageType.Backdrop, images);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,15 +24,15 @@
|
||||
|
||||
<ItemGroup>
|
||||
<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.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.Hosting.Abstractions" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.1" />
|
||||
<PackageReference Include="Mono.Nat" Version="3.0.2" />
|
||||
<PackageReference Include="prometheus-net.DotNetRuntime" Version="4.2.2" />
|
||||
<PackageReference Include="sharpcompress" Version="0.30.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.9" />
|
||||
<PackageReference Include="Mono.Nat" Version="3.0.3" />
|
||||
<PackageReference Include="prometheus-net.DotNetRuntime" Version="4.2.4" />
|
||||
<PackageReference Include="sharpcompress" Version="0.32.2" />
|
||||
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="3.1.0" />
|
||||
<PackageReference Include="DotNet.Glob" Version="3.1.3" />
|
||||
</ItemGroup>
|
||||
@@ -55,8 +55,12 @@
|
||||
|
||||
<!-- Code Analyzers-->
|
||||
<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="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.406" PrivateAssets="All" />
|
||||
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -326,7 +326,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
{
|
||||
var userIds = _sessionManager.Sessions
|
||||
.Select(i => i.UserId)
|
||||
.Where(i => !i.Equals(Guid.Empty))
|
||||
.Where(i => !i.Equals(default))
|
||||
.Distinct()
|
||||
.ToArray();
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@ using System.Net.Sockets;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Emby.Server.Implementations.Udp;
|
||||
using Jellyfin.Networking.Configuration;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Plugins;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
@@ -26,6 +28,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
private readonly ILogger<UdpServerEntryPoint> _logger;
|
||||
private readonly IServerApplicationHost _appHost;
|
||||
private readonly IConfiguration _config;
|
||||
private readonly IConfigurationManager _configurationManager;
|
||||
|
||||
/// <summary>
|
||||
/// 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="appHost">Instance of the <see cref="IServerApplicationHost"/> 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(
|
||||
ILogger<UdpServerEntryPoint> logger,
|
||||
IServerApplicationHost appHost,
|
||||
IConfiguration configuration)
|
||||
IConfiguration configuration,
|
||||
IConfigurationManager configurationManager)
|
||||
{
|
||||
_logger = logger;
|
||||
_appHost = appHost;
|
||||
_config = configuration;
|
||||
_configurationManager = configurationManager;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -55,6 +61,11 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
{
|
||||
CheckDisposed();
|
||||
|
||||
if (!_configurationManager.GetNetworkConfiguration().AutoDiscovery)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_udpServer = new UdpServer(_logger, _appHost, _config, PortNumber);
|
||||
|
||||
@@ -47,7 +47,9 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
||||
{
|
||||
var session = await GetSession(requestContext).ConfigureAwait(false);
|
||||
|
||||
return 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)
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
/// <summary>
|
||||
/// Class WebSocketConnection.
|
||||
/// </summary>
|
||||
public class WebSocketConnection : IWebSocketConnection, IDisposable
|
||||
public class WebSocketConnection : IWebSocketConnection
|
||||
{
|
||||
/// <summary>
|
||||
/// The logger.
|
||||
@@ -36,6 +36,8 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
/// </summary>
|
||||
private readonly WebSocket _socket;
|
||||
|
||||
private bool _disposed = false;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="WebSocketConnection" /> class.
|
||||
/// </summary>
|
||||
@@ -244,10 +246,39 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
/// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
|
||||
protected virtual void Dispose(bool dispose)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (dispose)
|
||||
{
|
||||
_socket.Dispose();
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
await DisposeAsyncCore().ConfigureAwait(false);
|
||||
Dispose(false);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to perform asynchronous cleanup of managed resources or for cascading calls to <see cref="DisposeAsync"/>.
|
||||
/// </summary>
|
||||
/// <returns>A ValueTask.</returns>
|
||||
protected virtual async ValueTask DisposeAsyncCore()
|
||||
{
|
||||
if (_socket.State == WebSocketState.Open)
|
||||
{
|
||||
await _socket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "System Shutdown", CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
_socket.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -262,6 +262,10 @@ namespace Emby.Server.Implementations.IO
|
||||
_logger.LogError(ex, "Reading the file size of the symlink at {Path} failed. Marking the file as not existing.", fileInfo.FullName);
|
||||
result.Exists = false;
|
||||
}
|
||||
catch (UnauthorizedAccessException ex)
|
||||
{
|
||||
_logger.LogError(ex, "Reading the file at {Path} failed due to a permissions exception.", fileInfo.FullName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -581,7 +585,7 @@ namespace Emby.Server.Implementations.IO
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual List<FileSystemMetadata> GetDrives()
|
||||
public virtual IEnumerable<FileSystemMetadata> GetDrives()
|
||||
{
|
||||
// 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
|
||||
@@ -595,7 +599,7 @@ namespace Emby.Server.Implementations.IO
|
||||
Name = d.Name,
|
||||
FullName = d.RootDirectory.FullName,
|
||||
IsDirectory = true
|
||||
}).ToList();
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -704,6 +708,18 @@ namespace Emby.Server.Implementations.IO
|
||||
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)
|
||||
{
|
||||
return new EnumerationOptions
|
||||
|
||||
@@ -135,7 +135,7 @@ namespace Emby.Server.Implementations.Images
|
||||
|
||||
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
|
||||
.Select(i =>
|
||||
{
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace Emby.Server.Implementations.Images
|
||||
{
|
||||
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)
|
||||
{
|
||||
_libraryManager = libraryManager;
|
||||
|
||||
@@ -42,6 +42,10 @@ namespace Emby.Server.Implementations.Images
|
||||
{
|
||||
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))
|
||||
{
|
||||
includeItemTypes = new[] { BaseItemKind.Book, BaseItemKind.AudioBook };
|
||||
|
||||
@@ -84,16 +84,20 @@ namespace Emby.Server.Implementations.Images
|
||||
}).GroupBy(x => x.Id)
|
||||
.Select(x => x.First());
|
||||
|
||||
List<BaseItem> returnItems;
|
||||
if (isUsingCollectionStrip)
|
||||
{
|
||||
return items
|
||||
returnItems = items
|
||||
.Where(i => i.HasImage(ImageType.Primary) || i.HasImage(ImageType.Thumb))
|
||||
.ToList();
|
||||
returnItems.Shuffle();
|
||||
return returnItems;
|
||||
}
|
||||
|
||||
return items
|
||||
returnItems = items
|
||||
.Where(i => i.HasImage(ImageType.Primary))
|
||||
.ToList();
|
||||
returnItems.Shuffle();
|
||||
return returnItems;
|
||||
}
|
||||
|
||||
protected override bool Supports(BaseItem item)
|
||||
|
||||
@@ -45,8 +45,8 @@ using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Library;
|
||||
using MediaBrowser.Model.Querying;
|
||||
using MediaBrowser.Model.Tasks;
|
||||
using MediaBrowser.Providers.MediaInfo;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Episode = MediaBrowser.Controller.Entities.TV.Episode;
|
||||
using EpisodeInfo = Emby.Naming.TV.EpisodeInfo;
|
||||
@@ -100,7 +100,7 @@ namespace Emby.Server.Implementations.Library
|
||||
/// Initializes a new instance of the <see cref="LibraryManager" /> class.
|
||||
/// </summary>
|
||||
/// <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="userManager">The user manager.</param>
|
||||
/// <param name="configurationManager">The configuration manager.</param>
|
||||
@@ -116,7 +116,7 @@ namespace Emby.Server.Implementations.Library
|
||||
/// <param name="namingOptions">The naming options.</param>
|
||||
public LibraryManager(
|
||||
IServerApplicationHost appHost,
|
||||
ILogger<LibraryManager> logger,
|
||||
ILoggerFactory loggerFactory,
|
||||
ITaskManager taskManager,
|
||||
IUserManager userManager,
|
||||
IServerConfigurationManager configurationManager,
|
||||
@@ -132,7 +132,7 @@ namespace Emby.Server.Implementations.Library
|
||||
NamingOptions namingOptions)
|
||||
{
|
||||
_appHost = appHost;
|
||||
_logger = logger;
|
||||
_logger = loggerFactory.CreateLogger<LibraryManager>();
|
||||
_taskManager = taskManager;
|
||||
_userManager = userManager;
|
||||
_configurationManager = configurationManager;
|
||||
@@ -147,7 +147,7 @@ namespace Emby.Server.Implementations.Library
|
||||
_memoryCache = memoryCache;
|
||||
_namingOptions = namingOptions;
|
||||
|
||||
_extraResolver = new ExtraResolver(namingOptions);
|
||||
_extraResolver = new ExtraResolver(loggerFactory.CreateLogger<ExtraResolver>(), namingOptions);
|
||||
|
||||
_configurationManager.ConfigurationUpdated += ConfigurationUpdated;
|
||||
|
||||
@@ -680,9 +680,7 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
if (result?.Items.Count > 0)
|
||||
{
|
||||
var items = new List<BaseItem>();
|
||||
items.AddRange(result.Items);
|
||||
|
||||
var items = result.Items;
|
||||
foreach (var item in items)
|
||||
{
|
||||
ResolverHelper.SetInitialItemValues(item, parent, this, directoryService);
|
||||
@@ -758,7 +756,7 @@ namespace Emby.Server.Implementations.Library
|
||||
Path = path
|
||||
};
|
||||
|
||||
if (folder.Id.Equals(Guid.Empty))
|
||||
if (folder.Id.Equals(default))
|
||||
{
|
||||
if (string.IsNullOrEmpty(folder.Path))
|
||||
{
|
||||
@@ -777,7 +775,7 @@ namespace Emby.Server.Implementations.Library
|
||||
folder = dbItem;
|
||||
}
|
||||
|
||||
if (folder.ParentId != rootFolder.Id)
|
||||
if (!folder.ParentId.Equals(rootFolder.Id))
|
||||
{
|
||||
folder.ParentId = rootFolder.Id;
|
||||
folder.UpdateToRepositoryAsync(ItemUpdateType.MetadataImport, CancellationToken.None).GetAwaiter().GetResult();
|
||||
@@ -1007,14 +1005,8 @@ namespace Emby.Server.Implementations.Library
|
||||
return GetNewItemIdInternal(path, typeof(T), forceCaseInsensitiveId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validate and refresh the People sub-set of the IBN.
|
||||
/// 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)
|
||||
/// <inheritdoc />
|
||||
public Task ValidatePeopleAsync(IProgress<double> progress, CancellationToken cancellationToken)
|
||||
{
|
||||
// Ensure the location is available.
|
||||
Directory.CreateDirectory(_configurationManager.ApplicationPaths.PeoplePath);
|
||||
@@ -1036,15 +1028,6 @@ namespace Emby.Server.Implementations.Library
|
||||
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>
|
||||
/// Validates the media library internal.
|
||||
/// </summary>
|
||||
@@ -1270,7 +1253,7 @@ namespace Emby.Server.Implementations.Library
|
||||
/// <exception cref="ArgumentNullException"><paramref name="id"/> is <c>null</c>.</exception>
|
||||
public BaseItem GetItemById(Guid id)
|
||||
{
|
||||
if (id == Guid.Empty)
|
||||
if (id.Equals(default))
|
||||
{
|
||||
throw new ArgumentException("Guid can't be empty", nameof(id));
|
||||
}
|
||||
@@ -1292,7 +1275,7 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
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);
|
||||
if (parent != null)
|
||||
@@ -1316,7 +1299,7 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
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);
|
||||
if (parent != null)
|
||||
@@ -1360,10 +1343,10 @@ namespace Emby.Server.Implementations.Library
|
||||
return _itemRepository.GetItems(query);
|
||||
}
|
||||
|
||||
return new QueryResult<BaseItem>
|
||||
{
|
||||
Items = _itemRepository.GetItemList(query)
|
||||
};
|
||||
return new QueryResult<BaseItem>(
|
||||
query.StartIndex,
|
||||
null,
|
||||
_itemRepository.GetItemList(query));
|
||||
}
|
||||
|
||||
public List<Guid> GetItemIds(InternalItemsQuery query)
|
||||
@@ -1444,7 +1427,7 @@ namespace Emby.Server.Implementations.Library
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
parents[i] = GetItemById(ancestorIds[i]);
|
||||
if (!(parents[i] is ICollectionFolder || parents[i] is UserView))
|
||||
if (parents[i] is not (ICollectionFolder or UserView))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -1474,7 +1457,7 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
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);
|
||||
if (parent != null)
|
||||
@@ -1493,10 +1476,10 @@ namespace Emby.Server.Implementations.Library
|
||||
return _itemRepository.GetItems(query);
|
||||
}
|
||||
|
||||
return new QueryResult<BaseItem>
|
||||
{
|
||||
Items = _itemRepository.GetItemList(query)
|
||||
};
|
||||
return new QueryResult<BaseItem>(
|
||||
query.StartIndex,
|
||||
null,
|
||||
_itemRepository.GetItemList(query));
|
||||
}
|
||||
|
||||
private void SetTopParentIdsOrAncestors(InternalItemsQuery query, List<BaseItem> parents)
|
||||
@@ -1530,7 +1513,7 @@ namespace Emby.Server.Implementations.Library
|
||||
private void AddUserToQuery(InternalItemsQuery query, User user, bool allowExternalContent = true)
|
||||
{
|
||||
if (query.AncestorIds.Length == 0 &&
|
||||
query.ParentId.Equals(Guid.Empty) &&
|
||||
query.ParentId.Equals(default) &&
|
||||
query.ChannelIds.Count == 0 &&
|
||||
query.TopParentIds.Length == 0 &&
|
||||
string.IsNullOrEmpty(query.AncestorWithPresentationUniqueKey) &&
|
||||
@@ -1558,7 +1541,7 @@ namespace Emby.Server.Implementations.Library
|
||||
}
|
||||
|
||||
// Translate view into folders
|
||||
if (!view.DisplayParentId.Equals(Guid.Empty))
|
||||
if (!view.DisplayParentId.Equals(default))
|
||||
{
|
||||
var displayParent = GetItemById(view.DisplayParentId);
|
||||
if (displayParent != null)
|
||||
@@ -1569,7 +1552,7 @@ namespace Emby.Server.Implementations.Library
|
||||
return Array.Empty<Guid>();
|
||||
}
|
||||
|
||||
if (!view.ParentId.Equals(Guid.Empty))
|
||||
if (!view.ParentId.Equals(default))
|
||||
{
|
||||
var displayParent = GetItemById(view.ParentId);
|
||||
if (displayParent != null)
|
||||
@@ -1650,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>
|
||||
/// Resolves the intro.
|
||||
/// </summary>
|
||||
@@ -1898,7 +1860,9 @@ namespace Emby.Server.Implementations.Library
|
||||
throw new ArgumentNullException(nameof(item));
|
||||
}
|
||||
|
||||
var outdated = forceUpdate ? item.ImageInfos.Where(i => i.Path != null).ToArray() : item.ImageInfos.Where(ImageNeedsRefresh).ToArray();
|
||||
var outdated = forceUpdate
|
||||
? item.ImageInfos.Where(i => i.Path != null).ToArray()
|
||||
: item.ImageInfos.Where(ImageNeedsRefresh).ToArray();
|
||||
// Skip image processing if current or live tv source
|
||||
if (outdated.Length == 0 || item.SourceType != SourceType.Library)
|
||||
{
|
||||
@@ -1921,7 +1885,7 @@ namespace Emby.Server.Implementations.Library
|
||||
_logger.LogWarning("Cannot get image index for {ImagePath}", img.Path);
|
||||
continue;
|
||||
}
|
||||
catch (Exception ex) when (ex is InvalidOperationException || ex is IOException)
|
||||
catch (Exception ex) when (ex is InvalidOperationException or IOException)
|
||||
{
|
||||
_logger.LogWarning(ex, "Cannot fetch image from {ImagePath}", img.Path);
|
||||
continue;
|
||||
@@ -1933,23 +1897,24 @@ namespace Emby.Server.Implementations.Library
|
||||
}
|
||||
}
|
||||
|
||||
ImageDimensions size;
|
||||
try
|
||||
{
|
||||
ImageDimensions size = _imageProcessor.GetImageDimensions(item, image);
|
||||
size = _imageProcessor.GetImageDimensions(item, image);
|
||||
image.Width = size.Width;
|
||||
image.Height = size.Height;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Cannot get image dimensions for {ImagePath}", image.Path);
|
||||
size = new ImageDimensions(0, 0);
|
||||
image.Width = 0;
|
||||
image.Height = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
image.BlurHash = _imageProcessor.GetImageBlurHash(image.Path);
|
||||
image.BlurHash = _imageProcessor.GetImageBlurHash(image.Path, size);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -2014,16 +1979,16 @@ namespace Emby.Server.Implementations.Library
|
||||
public Task UpdateItemAsync(BaseItem item, BaseItem parent, ItemUpdateType updateReason, CancellationToken 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)
|
||||
{
|
||||
ProviderManager.SaveMetadata(item, updateReason);
|
||||
await ProviderManager.SaveMetadataAsync(item, updateReason).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
item.DateLastSaved = DateTime.UtcNow;
|
||||
|
||||
return UpdateImagesAsync(item, updateReason >= ItemUpdateType.ImageUpdate);
|
||||
await UpdateImagesAsync(item, updateReason >= ItemUpdateType.ImageUpdate).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -2192,7 +2157,7 @@ namespace Emby.Server.Implementations.Library
|
||||
return null;
|
||||
}
|
||||
|
||||
while (!item.ParentId.Equals(Guid.Empty))
|
||||
while (!item.ParentId.Equals(default))
|
||||
{
|
||||
var parent = item.GetParent();
|
||||
if (parent == null || parent is AggregateFolder)
|
||||
@@ -2270,7 +2235,9 @@ namespace Emby.Server.Implementations.Library
|
||||
string viewType,
|
||||
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 id = GetNewItemId(idValues, typeof(UserView));
|
||||
@@ -2304,7 +2271,7 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
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);
|
||||
refresh = displayParent != null && displayParent.DateLastSaved > item.DateLastRefreshed;
|
||||
@@ -2371,7 +2338,7 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
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);
|
||||
refresh = displayParent != null && displayParent.DateLastSaved > item.DateLastRefreshed;
|
||||
@@ -2404,7 +2371,9 @@ namespace Emby.Server.Implementations.Library
|
||||
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);
|
||||
if (!string.IsNullOrEmpty(uniqueId))
|
||||
{
|
||||
@@ -2448,7 +2417,7 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
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);
|
||||
refresh = displayParent != null && displayParent.DateLastSaved > item.DateLastRefreshed;
|
||||
@@ -2469,24 +2438,6 @@ namespace Emby.Server.Implementations.Library
|
||||
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)
|
||||
{
|
||||
if (parentId.HasValue)
|
||||
@@ -2494,7 +2445,7 @@ namespace Emby.Server.Implementations.Library
|
||||
return GetItemById(parentId.Value);
|
||||
}
|
||||
|
||||
if (userId.HasValue && userId != Guid.Empty)
|
||||
if (userId.HasValue && !userId.Equals(default))
|
||||
{
|
||||
return GetUserRootFolder();
|
||||
}
|
||||
@@ -2502,6 +2453,12 @@ namespace Emby.Server.Implementations.Library
|
||||
return RootFolder;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void QueueLibraryScan()
|
||||
{
|
||||
_taskManager.QueueScheduledTask<RefreshMediaLibraryTask>();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public int? GetSeasonNumberFromPath(string path)
|
||||
=> SeasonPathParser.Parse(path, true, true).SeasonNumber;
|
||||
@@ -2686,7 +2643,7 @@ namespace Emby.Server.Implementations.Library
|
||||
};
|
||||
}
|
||||
|
||||
public IEnumerable<BaseItem> FindExtras(BaseItem owner, List<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService)
|
||||
public IEnumerable<BaseItem> FindExtras(BaseItem owner, IReadOnlyList<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService)
|
||||
{
|
||||
var ownerVideoInfo = VideoResolver.Resolve(owner.Path, owner.IsFolder, _namingOptions);
|
||||
if (ownerVideoInfo == null)
|
||||
@@ -2784,16 +2741,6 @@ namespace Emby.Server.Implementations.Library
|
||||
return path;
|
||||
}
|
||||
|
||||
public string SubstitutePath(string path, string from, string to)
|
||||
{
|
||||
if (path.TryReplaceSubPath(from, to, out var newPath))
|
||||
{
|
||||
return newPath;
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
public List<PersonInfo> GetPeople(InternalPeopleQuery query)
|
||||
{
|
||||
return _itemRepository.GetPeople(query);
|
||||
@@ -2819,7 +2766,8 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
public List<Person> GetPeopleItems(InternalPeopleQuery query)
|
||||
{
|
||||
return _itemRepository.GetPeopleNames(query).Select(i =>
|
||||
return _itemRepository.GetPeopleNames(query)
|
||||
.Select(i =>
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -2830,7 +2778,12 @@ namespace Emby.Server.Implementations.Library
|
||||
_logger.LogError(ex, "Error getting person");
|
||||
return null;
|
||||
}
|
||||
}).Where(i => i != null).ToList();
|
||||
})
|
||||
.Where(i => i != null)
|
||||
.Where(i => query.User == null ?
|
||||
true :
|
||||
i.IsVisible(query.User))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public List<string> GetPeopleNames(InternalPeopleQuery query)
|
||||
@@ -2902,10 +2855,12 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
var existingNameCount = 1; // first numbered name will be 2
|
||||
var virtualFolderPath = Path.Combine(rootFolderPath, name);
|
||||
var originalName = name;
|
||||
while (Directory.Exists(virtualFolderPath))
|
||||
{
|
||||
existingNameCount++;
|
||||
virtualFolderPath = Path.Combine(rootFolderPath, name + " " + existingNameCount);
|
||||
name = originalName + existingNameCount;
|
||||
virtualFolderPath = Path.Combine(rootFolderPath, name);
|
||||
}
|
||||
|
||||
var mediaPathInfos = options.PathInfos;
|
||||
@@ -3007,7 +2962,10 @@ namespace Emby.Server.Implementations.Library
|
||||
}
|
||||
}
|
||||
|
||||
CreateItems(personsToSave, null, CancellationToken.None);
|
||||
if (personsToSave.Count > 0)
|
||||
{
|
||||
CreateItems(personsToSave, null, CancellationToken.None);
|
||||
}
|
||||
}
|
||||
|
||||
private void StartScanInBackground()
|
||||
|
||||
@@ -151,7 +151,11 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
var mediaSources = GetStaticMediaSources(item, enablePathSubstitution, user);
|
||||
|
||||
if (allowMediaProbe && mediaSources[0].Type != MediaSourceType.Placeholder && !mediaSources[0].MediaStreams.Any(i => i.Type == MediaStreamType.Audio || i.Type == MediaStreamType.Video))
|
||||
// If file is strm or main media stream is missing, force a metadata refresh with remote probing
|
||||
if (allowMediaProbe && mediaSources[0].Type != MediaSourceType.Placeholder
|
||||
&& (item.Path.EndsWith(".strm", StringComparison.OrdinalIgnoreCase)
|
||||
|| (item.MediaType == MediaType.Video && !mediaSources[0].MediaStreams.Any(i => i.Type == MediaStreamType.Video))
|
||||
|| (item.MediaType == MediaType.Audio && !mediaSources[0].MediaStreams.Any(i => i.Type == MediaStreamType.Audio))))
|
||||
{
|
||||
await item.RefreshMetadata(
|
||||
new MetadataRefreshOptions(_directoryService)
|
||||
@@ -172,24 +176,16 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
foreach (var source in dynamicMediaSources)
|
||||
{
|
||||
if (user != null)
|
||||
{
|
||||
SetDefaultAudioAndSubtitleStreamIndexes(item, source, user);
|
||||
}
|
||||
|
||||
// Validate that this is actually possible
|
||||
if (source.SupportsDirectStream)
|
||||
{
|
||||
source.SupportsDirectStream = SupportsDirectStream(source.Path, source.Protocol);
|
||||
}
|
||||
|
||||
list.Add(source);
|
||||
}
|
||||
|
||||
if (user != null)
|
||||
{
|
||||
foreach (var source in list)
|
||||
if (user != null)
|
||||
{
|
||||
SetDefaultAudioAndSubtitleStreamIndexes(item, source, user);
|
||||
|
||||
if (string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
source.SupportsTranscoding = user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding);
|
||||
@@ -200,6 +196,8 @@ namespace Emby.Server.Implementations.Library
|
||||
source.SupportsDirectStream = user.HasPermission(PermissionKind.EnablePlaybackRemuxing);
|
||||
}
|
||||
}
|
||||
|
||||
list.Add(source);
|
||||
}
|
||||
|
||||
return SortMediaSources(list);
|
||||
@@ -338,13 +336,23 @@ namespace Emby.Server.Implementations.Library
|
||||
foreach (var source in sources)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
private string[] NormalizeLanguage(string language)
|
||||
private IReadOnlyList<string> NormalizeLanguage(string language)
|
||||
{
|
||||
if (string.IsNullOrEmpty(language))
|
||||
{
|
||||
@@ -514,10 +522,10 @@ namespace Emby.Server.Implementations.Library
|
||||
_logger.LogInformation("Live stream opened: {@MediaSource}", mediaSource);
|
||||
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 item = request.ItemId.Equals(Guid.Empty)
|
||||
var item = request.ItemId.Equals(default)
|
||||
? null
|
||||
: _libraryManager.GetItemById(request.ItemId);
|
||||
SetDefaultAudioAndSubtitleStreamIndexes(item, clone, user);
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
#nullable disable
|
||||
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
@@ -13,14 +11,13 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
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)
|
||||
.ToList();
|
||||
var sortedStreams = GetSortedStreams(streams, MediaStreamType.Audio, preferredLanguages).ToList();
|
||||
|
||||
if (preferDefaultTrack)
|
||||
{
|
||||
var defaultStream = streams.FirstOrDefault(i => i.IsDefault);
|
||||
var defaultStream = sortedStreams.FirstOrDefault(i => i.IsDefault);
|
||||
|
||||
if (defaultStream != null)
|
||||
{
|
||||
@@ -28,24 +25,15 @@ namespace Emby.Server.Implementations.Library
|
||||
}
|
||||
}
|
||||
|
||||
var stream = streams.FirstOrDefault();
|
||||
|
||||
if (stream != null)
|
||||
{
|
||||
return stream.Index;
|
||||
}
|
||||
|
||||
return null;
|
||||
return sortedStreams.FirstOrDefault()?.Index;
|
||||
}
|
||||
|
||||
public static int? GetDefaultSubtitleStreamIndex(
|
||||
IEnumerable<MediaStream> streams,
|
||||
string[] preferredLanguages,
|
||||
IReadOnlyList<string> preferredLanguages,
|
||||
SubtitlePlaybackMode mode,
|
||||
string audioTrackLanguage)
|
||||
{
|
||||
MediaStream stream = null;
|
||||
|
||||
if (mode == SubtitlePlaybackMode.None)
|
||||
{
|
||||
return null;
|
||||
@@ -59,6 +47,7 @@ namespace Emby.Server.Implementations.Library
|
||||
.ThenByDescending(x => x.IsDefault)
|
||||
.ToList();
|
||||
|
||||
MediaStream? stream = null;
|
||||
if (mode == SubtitlePlaybackMode.Default)
|
||||
{
|
||||
// Prefer embedded metadata over smart logic
|
||||
@@ -95,26 +84,27 @@ namespace Emby.Server.Implementations.Library
|
||||
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
|
||||
return streams.Where(i => i.Type == type)
|
||||
return streams
|
||||
.Where(i => i.Type == type)
|
||||
.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;
|
||||
})
|
||||
.ThenBy(i => GetBooleanOrderBy(i.IsDefault))
|
||||
.ThenBy(i => GetBooleanOrderBy(i.SupportsExternalStream))
|
||||
.ThenBy(i => GetBooleanOrderBy(i.IsTextSubtitleStream))
|
||||
.ThenBy(i => GetBooleanOrderBy(i.IsExternal))
|
||||
.ThenBy(i => i.Index);
|
||||
return index == -1 ? 100 : index;
|
||||
})
|
||||
.ThenBy(i => GetBooleanOrderBy(i.IsDefault))
|
||||
.ThenBy(i => GetBooleanOrderBy(i.SupportsExternalStream))
|
||||
.ThenBy(i => GetBooleanOrderBy(i.IsTextSubtitleStream))
|
||||
.ThenBy(i => GetBooleanOrderBy(i.IsExternal))
|
||||
.ThenBy(i => i.Index);
|
||||
}
|
||||
|
||||
public static void SetSubtitleStreamScores(
|
||||
List<MediaStream> streams,
|
||||
string[] preferredLanguages,
|
||||
IReadOnlyList<MediaStream> streams,
|
||||
IReadOnlyList<string> preferredLanguages,
|
||||
SubtitlePlaybackMode mode,
|
||||
string audioTrackLanguage)
|
||||
{
|
||||
@@ -123,15 +113,14 @@ namespace Emby.Server.Implementations.Library
|
||||
return;
|
||||
}
|
||||
|
||||
streams = GetSortedStreams(streams, MediaStreamType.Subtitle, preferredLanguages)
|
||||
.ToList();
|
||||
var sortedStreams = GetSortedStreams(streams, MediaStreamType.Subtitle, preferredLanguages);
|
||||
|
||||
var filteredStreams = new List<MediaStream>();
|
||||
|
||||
if (mode == SubtitlePlaybackMode.Default)
|
||||
{
|
||||
// Prefer embedded metadata over smart logic
|
||||
filteredStreams = streams.Where(s => s.IsForced || s.IsDefault)
|
||||
filteredStreams = sortedStreams.Where(s => s.IsForced || s.IsDefault)
|
||||
.ToList();
|
||||
}
|
||||
else if (mode == SubtitlePlaybackMode.Smart)
|
||||
@@ -139,54 +128,37 @@ namespace Emby.Server.Implementations.Library
|
||||
// Prefer smart logic over embedded metadata
|
||||
if (!preferredLanguages.Contains(audioTrackLanguage, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
filteredStreams = streams.Where(s => !s.IsForced && preferredLanguages.Contains(s.Language, StringComparison.OrdinalIgnoreCase))
|
||||
filteredStreams = sortedStreams.Where(s => !s.IsForced && preferredLanguages.Contains(s.Language, StringComparison.OrdinalIgnoreCase))
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
else if (mode == SubtitlePlaybackMode.Always)
|
||||
{
|
||||
// always load the most suitable full subtitles
|
||||
filteredStreams = streams.Where(s => !s.IsForced)
|
||||
.ToList();
|
||||
filteredStreams = sortedStreams.Where(s => !s.IsForced).ToList();
|
||||
}
|
||||
else if (mode == SubtitlePlaybackMode.OnlyForced)
|
||||
{
|
||||
// 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
|
||||
if (filteredStreams.Count == 0)
|
||||
{
|
||||
filteredStreams = streams
|
||||
.Where(s => s.IsForced && string.Equals(s.Language, audioTrackLanguage, StringComparison.OrdinalIgnoreCase))
|
||||
.ToList();
|
||||
}
|
||||
var iterStreams = filteredStreams.Count == 0
|
||||
? sortedStreams.Where(s => s.IsForced && string.Equals(s.Language, audioTrackLanguage, StringComparison.OrdinalIgnoreCase))
|
||||
: filteredStreams;
|
||||
|
||||
foreach (var stream in filteredStreams)
|
||||
foreach (var stream in iterStreams)
|
||||
{
|
||||
stream.Score = GetSubtitleScore(stream, preferredLanguages);
|
||||
}
|
||||
}
|
||||
|
||||
private static int FindIndex(string[] list, string value)
|
||||
{
|
||||
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)
|
||||
private static int GetSubtitleScore(MediaStream stream, IReadOnlyList<string> languagePreferences)
|
||||
{
|
||||
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);
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
return Guid.Empty;
|
||||
}
|
||||
}).Where(i => !i.Equals(Guid.Empty)).ToArray();
|
||||
}).Where(i => !i.Equals(default)).ToArray();
|
||||
|
||||
return GetInstantMixFromGenreIds(genreIds, user, dtoOptions);
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Server.Implementations.Library.Resolvers
|
||||
{
|
||||
@@ -22,8 +23,11 @@ namespace Emby.Server.Implementations.Library.Resolvers
|
||||
public abstract class BaseVideoResolver<T> : MediaBrowser.Controller.Resolvers.ItemResolver<T>
|
||||
where T : Video, new()
|
||||
{
|
||||
protected BaseVideoResolver(NamingOptions namingOptions)
|
||||
private readonly ILogger _logger;
|
||||
|
||||
protected BaseVideoResolver(ILogger logger, NamingOptions namingOptions)
|
||||
{
|
||||
_logger = logger;
|
||||
NamingOptions = namingOptions;
|
||||
}
|
||||
|
||||
@@ -156,19 +160,26 @@ namespace Emby.Server.Implementations.Library.Resolvers
|
||||
}
|
||||
else
|
||||
{
|
||||
// use disc-utils, both DVDs and BDs use UDF filesystem
|
||||
using (var videoFileStream = File.Open(video.Path, FileMode.Open, FileAccess.Read))
|
||||
using (UdfReader udfReader = new UdfReader(videoFileStream))
|
||||
try
|
||||
{
|
||||
if (udfReader.DirectoryExists("VIDEO_TS"))
|
||||
// use disc-utils, both DVDs and BDs use UDF filesystem
|
||||
using (var videoFileStream = File.Open(video.Path, FileMode.Open, FileAccess.Read))
|
||||
using (UdfReader udfReader = new UdfReader(videoFileStream))
|
||||
{
|
||||
video.IsoType = IsoType.Dvd;
|
||||
}
|
||||
else if (udfReader.DirectoryExists("BDMV"))
|
||||
{
|
||||
video.IsoType = IsoType.BluRay;
|
||||
if (udfReader.DirectoryExists("VIDEO_TS"))
|
||||
{
|
||||
video.IsoType = IsoType.Dvd;
|
||||
}
|
||||
else if (udfReader.DirectoryExists("BDMV"))
|
||||
{
|
||||
video.IsoType = IsoType.BluRay;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error opening UDF/ISO image: {Value}", video.Path ?? video.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ using Emby.Naming.Video;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Resolvers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using static Emby.Naming.Video.ExtraRuleResolver;
|
||||
|
||||
namespace Emby.Server.Implementations.Library.Resolvers
|
||||
@@ -20,14 +21,15 @@ namespace Emby.Server.Implementations.Library.Resolvers
|
||||
private readonly IItemResolver[] _videoResolvers;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes an new instance of the <see cref="ExtraResolver"/> class.
|
||||
/// Initializes a new instance of the <see cref="ExtraResolver"/> class.
|
||||
/// </summary>
|
||||
/// <param name="logger">The logger.</param>
|
||||
/// <param name="namingOptions">An instance of <see cref="NamingOptions"/>.</param>
|
||||
public ExtraResolver(NamingOptions namingOptions)
|
||||
public ExtraResolver(ILogger<ExtraResolver> logger, NamingOptions namingOptions)
|
||||
{
|
||||
_namingOptions = namingOptions;
|
||||
_trailerResolvers = new IItemResolver[] { new GenericVideoResolver<Trailer>(namingOptions) };
|
||||
_videoResolvers = new IItemResolver[] { new GenericVideoResolver<Video>(namingOptions) };
|
||||
_trailerResolvers = new IItemResolver[] { new GenericVideoResolver<Trailer>(logger, namingOptions) };
|
||||
_videoResolvers = new IItemResolver[] { new GenericVideoResolver<Video>(logger, namingOptions) };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
#nullable disable
|
||||
#nullable disable
|
||||
|
||||
using Emby.Naming.Common;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Server.Implementations.Library.Resolvers
|
||||
{
|
||||
@@ -15,9 +16,10 @@ namespace Emby.Server.Implementations.Library.Resolvers
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GenericVideoResolver{T}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="logger">The logger.</param>
|
||||
/// <param name="namingOptions">The naming options.</param>
|
||||
public GenericVideoResolver(NamingOptions namingOptions)
|
||||
: base(namingOptions)
|
||||
public GenericVideoResolver(ILogger logger, NamingOptions namingOptions)
|
||||
: base(logger, namingOptions)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Controller.Resolvers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.IO;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
||||
{
|
||||
@@ -32,7 +33,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
||||
CollectionType.Movies,
|
||||
CollectionType.HomeVideos,
|
||||
CollectionType.MusicVideos,
|
||||
CollectionType.Movies,
|
||||
CollectionType.TvShows,
|
||||
CollectionType.Photos
|
||||
};
|
||||
|
||||
@@ -40,9 +41,10 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
||||
/// Initializes a new instance of the <see cref="MovieResolver"/> class.
|
||||
/// </summary>
|
||||
/// <param name="imageProcessor">The image processor.</param>
|
||||
/// <param name="logger">The logger.</param>
|
||||
/// <param name="namingOptions">The naming options.</param>
|
||||
public MovieResolver(IImageProcessor imageProcessor, NamingOptions namingOptions)
|
||||
: base(namingOptions)
|
||||
public MovieResolver(IImageProcessor imageProcessor, ILogger<MovieResolver> logger, NamingOptions namingOptions)
|
||||
: base(logger, namingOptions)
|
||||
{
|
||||
_imageProcessor = imageProcessor;
|
||||
}
|
||||
@@ -128,10 +130,9 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
||||
return movie?.ExtraType == null ? movie : null;
|
||||
}
|
||||
|
||||
// Owned items will be caught by the video extra resolver
|
||||
if (args.Parent == null)
|
||||
{
|
||||
return null;
|
||||
return base.Resolve(args);
|
||||
}
|
||||
|
||||
if (IsInvalid(args.Parent, collectionType))
|
||||
@@ -222,6 +223,11 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
||||
return ResolveVideos<Movie>(parent, files, true, collectionType, true);
|
||||
}
|
||||
|
||||
if (string.Equals(collectionType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return ResolveVideos<Episode>(parent, files, false, collectionType, true);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ using Emby.Naming.Common;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Server.Implementations.Library.Resolvers.TV
|
||||
{
|
||||
@@ -17,9 +18,10 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="EpisodeResolver"/> class.
|
||||
/// </summary>
|
||||
/// <param name="logger">The logger.</param>
|
||||
/// <param name="namingOptions">The naming options.</param>
|
||||
public EpisodeResolver(NamingOptions namingOptions)
|
||||
: base(namingOptions)
|
||||
public EpisodeResolver(ILogger<EpisodeResolver> logger, NamingOptions namingOptions)
|
||||
: base(logger, namingOptions)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Diacritics.Extensions;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
@@ -30,7 +30,7 @@ namespace Emby.Server.Implementations.Library
|
||||
public QueryResult<SearchHintInfo> GetSearchHints(SearchQuery query)
|
||||
{
|
||||
User user = null;
|
||||
if (query.UserId != Guid.Empty)
|
||||
if (!query.UserId.Equals(default))
|
||||
{
|
||||
user = _userManager.GetUserById(query.UserId);
|
||||
}
|
||||
@@ -48,12 +48,10 @@ namespace Emby.Server.Implementations.Library
|
||||
results = results.GetRange(0, Math.Min(query.Limit.Value, results.Count));
|
||||
}
|
||||
|
||||
return new QueryResult<SearchHintInfo>
|
||||
{
|
||||
TotalRecordCount = totalRecordCount,
|
||||
|
||||
Items = results
|
||||
};
|
||||
return new QueryResult<SearchHintInfo>(
|
||||
query.StartIndex,
|
||||
totalRecordCount,
|
||||
results);
|
||||
}
|
||||
|
||||
private static void AddIfMissing(List<BaseItemKind> list, BaseItemKind value)
|
||||
@@ -170,10 +168,10 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
Fields = new ItemFields[]
|
||||
{
|
||||
ItemFields.AirTime,
|
||||
ItemFields.DateCreated,
|
||||
ItemFields.ChannelInfo,
|
||||
ItemFields.ParentId
|
||||
ItemFields.AirTime,
|
||||
ItemFields.DateCreated,
|
||||
ItemFields.ChannelInfo,
|
||||
ItemFields.ParentId
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -182,12 +180,12 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
if (searchQuery.IncludeItemTypes.Length == 1 && searchQuery.IncludeItemTypes[0] == BaseItemKind.MusicArtist)
|
||||
{
|
||||
if (!searchQuery.ParentId.Equals(Guid.Empty))
|
||||
if (!searchQuery.ParentId.Equals(default))
|
||||
{
|
||||
searchQuery.AncestorIds = new[] { searchQuery.ParentId };
|
||||
searchQuery.ParentId = Guid.Empty;
|
||||
}
|
||||
|
||||
searchQuery.ParentId = Guid.Empty;
|
||||
searchQuery.IncludeItemsByName = true;
|
||||
searchQuery.IncludeItemTypes = Array.Empty<BaseItemKind>();
|
||||
mediaItems = _libraryManager.GetAllArtists(searchQuery).Items.Select(i => i.Item).ToList();
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Controller.Drawing;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Persistence;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Querying;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Server.Implementations.Library;
|
||||
|
||||
/// <summary>
|
||||
/// The splashscreen post scan task.
|
||||
/// </summary>
|
||||
public class SplashscreenPostScanTask : ILibraryPostScanTask
|
||||
{
|
||||
private readonly IItemRepository _itemRepository;
|
||||
private readonly IImageEncoder _imageEncoder;
|
||||
private readonly ILogger<SplashscreenPostScanTask> _logger;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SplashscreenPostScanTask"/> class.
|
||||
/// </summary>
|
||||
/// <param name="itemRepository">Instance of the <see cref="IItemRepository"/> interface.</param>
|
||||
/// <param name="imageEncoder">Instance of the <see cref="IImageEncoder"/> interface.</param>
|
||||
/// <param name="logger">Instance of the <see cref="ILogger{SplashscreenPostScanTask}"/> interface.</param>
|
||||
public SplashscreenPostScanTask(
|
||||
IItemRepository itemRepository,
|
||||
IImageEncoder imageEncoder,
|
||||
ILogger<SplashscreenPostScanTask> logger)
|
||||
{
|
||||
_itemRepository = itemRepository;
|
||||
_imageEncoder = imageEncoder;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task Run(IProgress<double> progress, CancellationToken cancellationToken)
|
||||
{
|
||||
var posters = GetItemsWithImageType(ImageType.Primary).Select(x => x.GetImages(ImageType.Primary).First().Path).ToList();
|
||||
var backdrops = GetItemsWithImageType(ImageType.Thumb).Select(x => x.GetImages(ImageType.Thumb).First().Path).ToList();
|
||||
if (backdrops.Count == 0)
|
||||
{
|
||||
// Thumb images fit better because they include the title in the image but are not provided with TMDb.
|
||||
// Using backdrops as a fallback to generate an image at all
|
||||
_logger.LogDebug("No thumb images found. Using backdrops to generate splashscreen");
|
||||
backdrops = GetItemsWithImageType(ImageType.Backdrop).Select(x => x.GetImages(ImageType.Backdrop).First().Path).ToList();
|
||||
}
|
||||
|
||||
_imageEncoder.CreateSplashscreen(posters, backdrops);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private IReadOnlyList<BaseItem> GetItemsWithImageType(ImageType imageType)
|
||||
{
|
||||
// TODO make included libraries configurable
|
||||
return _itemRepository.GetItemList(new InternalItemsQuery
|
||||
{
|
||||
CollapseBoxSetItems = false,
|
||||
Recursive = true,
|
||||
DtoOptions = new DtoOptions(false),
|
||||
ImageTypes = new[] { imageType },
|
||||
Limit = 30,
|
||||
// TODO max parental rating configurable
|
||||
MaxParentalRating = 10,
|
||||
OrderBy = new[]
|
||||
{
|
||||
(ItemSortBy.Random, SortOrder.Ascending)
|
||||
},
|
||||
IncludeItemTypes = new[] { BaseItemKind.Movie, BaseItemKind.Series }
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -142,7 +142,7 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
if (index == -1
|
||||
&& i is UserView view
|
||||
&& view.DisplayParentId != Guid.Empty)
|
||||
&& !view.DisplayParentId.Equals(default))
|
||||
{
|
||||
index = Array.IndexOf(orders, view.DisplayParentId);
|
||||
}
|
||||
@@ -214,7 +214,7 @@ namespace Emby.Server.Implementations.Library
|
||||
}
|
||||
else
|
||||
{
|
||||
var current = list.FirstOrDefault(i => i.Item1 != null && i.Item1.Id == container.Id);
|
||||
var current = list.FirstOrDefault(i => i.Item1 != null && i.Item1.Id.Equals(container.Id));
|
||||
|
||||
if (current != null)
|
||||
{
|
||||
@@ -244,7 +244,7 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
var parents = new List<BaseItem>();
|
||||
|
||||
if (!parentId.Equals(Guid.Empty))
|
||||
if (!parentId.Equals(default))
|
||||
{
|
||||
var parentItem = _libraryManager.GetItemById(parentId);
|
||||
if (parentItem is Channel)
|
||||
|
||||
@@ -46,7 +46,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(targetFile) ?? throw new ArgumentException("Path can't be a root directory.", nameof(targetFile)));
|
||||
|
||||
using (var output = new FileStream(targetFile, FileMode.CreateNew, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous))
|
||||
await using (var output = new FileStream(targetFile, FileMode.CreateNew, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous))
|
||||
{
|
||||
onStarted();
|
||||
|
||||
@@ -56,14 +56,16 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
using var durationToken = new CancellationTokenSource(duration);
|
||||
using var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token);
|
||||
var linkedCancellationToken = cancellationTokenSource.Token;
|
||||
|
||||
await using var fileStream = new ProgressiveFileStream(directStreamProvider.GetStream());
|
||||
await _streamHelper.CopyToAsync(
|
||||
fileStream,
|
||||
output,
|
||||
IODefaults.CopyToBufferSize,
|
||||
1000,
|
||||
linkedCancellationToken).ConfigureAwait(false);
|
||||
var fileStream = new ProgressiveFileStream(directStreamProvider.GetStream());
|
||||
await using (fileStream.ConfigureAwait(false))
|
||||
{
|
||||
await _streamHelper.CopyToAsync(
|
||||
fileStream,
|
||||
output,
|
||||
IODefaults.CopyToBufferSize,
|
||||
1000,
|
||||
linkedCancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
_logger.LogInformation("Recording completed: {FilePath}", targetFile);
|
||||
|
||||
@@ -1819,16 +1819,16 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
|
||||
if (timer.IsProgramSeries)
|
||||
{
|
||||
SaveSeriesNfo(timer, seriesPath);
|
||||
SaveVideoNfo(timer, recordingPath, program, false);
|
||||
await SaveSeriesNfoAsync(timer, seriesPath).ConfigureAwait(false);
|
||||
await SaveVideoNfoAsync(timer, recordingPath, program, false).ConfigureAwait(false);
|
||||
}
|
||||
else if (!timer.IsMovie || timer.IsSports || timer.IsNews)
|
||||
{
|
||||
SaveVideoNfo(timer, recordingPath, program, true);
|
||||
await SaveVideoNfoAsync(timer, recordingPath, program, true).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
SaveVideoNfo(timer, recordingPath, program, false);
|
||||
await SaveVideoNfoAsync(timer, recordingPath, program, false).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
await SaveRecordingImages(recordingPath, program).ConfigureAwait(false);
|
||||
@@ -1839,7 +1839,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
}
|
||||
}
|
||||
|
||||
private void SaveSeriesNfo(TimerInfo timer, string seriesPath)
|
||||
private async Task SaveSeriesNfoAsync(TimerInfo timer, string seriesPath)
|
||||
{
|
||||
var nfoPath = Path.Combine(seriesPath, "tvshow.nfo");
|
||||
|
||||
@@ -1848,61 +1848,62 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
return;
|
||||
}
|
||||
|
||||
using (var stream = new FileStream(nfoPath, FileMode.CreateNew, FileAccess.Write, FileShare.None))
|
||||
await using (var stream = new FileStream(nfoPath, FileMode.CreateNew, FileAccess.Write, FileShare.None))
|
||||
{
|
||||
var settings = new XmlWriterSettings
|
||||
{
|
||||
Indent = true,
|
||||
Encoding = Encoding.UTF8
|
||||
Encoding = Encoding.UTF8,
|
||||
Async = true
|
||||
};
|
||||
|
||||
using (var writer = XmlWriter.Create(stream, settings))
|
||||
await using (var writer = XmlWriter.Create(stream, settings))
|
||||
{
|
||||
writer.WriteStartDocument(true);
|
||||
writer.WriteStartElement("tvshow");
|
||||
await writer.WriteStartDocumentAsync(true).ConfigureAwait(false);
|
||||
await writer.WriteStartElementAsync(null, "tvshow", null).ConfigureAwait(false);
|
||||
string id;
|
||||
if (timer.SeriesProviderIds.TryGetValue(MetadataProvider.Tvdb.ToString(), out id))
|
||||
{
|
||||
writer.WriteElementString("id", id);
|
||||
await writer.WriteElementStringAsync(null, "id", null, id).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (timer.SeriesProviderIds.TryGetValue(MetadataProvider.Imdb.ToString(), out id))
|
||||
{
|
||||
writer.WriteElementString("imdb_id", id);
|
||||
await writer.WriteElementStringAsync(null, "imdb_id", null, id).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (timer.SeriesProviderIds.TryGetValue(MetadataProvider.Tmdb.ToString(), out id))
|
||||
{
|
||||
writer.WriteElementString("tmdbid", id);
|
||||
await writer.WriteElementStringAsync(null, "tmdbid", null, id).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (timer.SeriesProviderIds.TryGetValue(MetadataProvider.Zap2It.ToString(), out id))
|
||||
{
|
||||
writer.WriteElementString("zap2itid", id);
|
||||
await writer.WriteElementStringAsync(null, "zap2itid", null, id).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(timer.Name))
|
||||
{
|
||||
writer.WriteElementString("title", timer.Name);
|
||||
await writer.WriteElementStringAsync(null, "title", null, timer.Name).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(timer.OfficialRating))
|
||||
{
|
||||
writer.WriteElementString("mpaa", timer.OfficialRating);
|
||||
await writer.WriteElementStringAsync(null, "mpaa", null, timer.OfficialRating).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
foreach (var genre in timer.Genres)
|
||||
{
|
||||
writer.WriteElementString("genre", genre);
|
||||
await writer.WriteElementStringAsync(null, "genre", null, genre).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
writer.WriteEndElement();
|
||||
writer.WriteEndDocument();
|
||||
await writer.WriteEndElementAsync().ConfigureAwait(false);
|
||||
await writer.WriteEndDocumentAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SaveVideoNfo(TimerInfo timer, string recordingPath, BaseItem item, bool lockData)
|
||||
private async Task SaveVideoNfoAsync(TimerInfo timer, string recordingPath, BaseItem item, bool lockData)
|
||||
{
|
||||
var nfoPath = Path.ChangeExtension(recordingPath, ".nfo");
|
||||
|
||||
@@ -1911,29 +1912,30 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
return;
|
||||
}
|
||||
|
||||
using (var stream = new FileStream(nfoPath, FileMode.CreateNew, FileAccess.Write, FileShare.None))
|
||||
await using (var stream = new FileStream(nfoPath, FileMode.CreateNew, FileAccess.Write, FileShare.None))
|
||||
{
|
||||
var settings = new XmlWriterSettings
|
||||
{
|
||||
Indent = true,
|
||||
Encoding = Encoding.UTF8
|
||||
Encoding = Encoding.UTF8,
|
||||
Async = true
|
||||
};
|
||||
|
||||
var options = _config.GetNfoConfiguration();
|
||||
|
||||
var isSeriesEpisode = timer.IsProgramSeries;
|
||||
|
||||
using (var writer = XmlWriter.Create(stream, settings))
|
||||
await using (var writer = XmlWriter.Create(stream, settings))
|
||||
{
|
||||
writer.WriteStartDocument(true);
|
||||
await writer.WriteStartDocumentAsync(true).ConfigureAwait(false);
|
||||
|
||||
if (isSeriesEpisode)
|
||||
{
|
||||
writer.WriteStartElement("episodedetails");
|
||||
await writer.WriteStartElementAsync(null, "episodedetails", null).ConfigureAwait(false);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(timer.EpisodeTitle))
|
||||
{
|
||||
writer.WriteElementString("title", timer.EpisodeTitle);
|
||||
await writer.WriteElementStringAsync(null, "title", null, timer.EpisodeTitle).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
var premiereDate = item.PremiereDate ?? (!timer.IsRepeat ? DateTime.UtcNow : null);
|
||||
@@ -1942,79 +1944,87 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
{
|
||||
var formatString = options.ReleaseDateFormat;
|
||||
|
||||
writer.WriteElementString(
|
||||
await writer.WriteElementStringAsync(
|
||||
null,
|
||||
"aired",
|
||||
premiereDate.Value.ToLocalTime().ToString(formatString, CultureInfo.InvariantCulture));
|
||||
null,
|
||||
premiereDate.Value.ToLocalTime().ToString(formatString, CultureInfo.InvariantCulture)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (item.IndexNumber.HasValue)
|
||||
{
|
||||
writer.WriteElementString("episode", item.IndexNumber.Value.ToString(CultureInfo.InvariantCulture));
|
||||
await writer.WriteElementStringAsync(null, "episode", null, item.IndexNumber.Value.ToString(CultureInfo.InvariantCulture)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (item.ParentIndexNumber.HasValue)
|
||||
{
|
||||
writer.WriteElementString("season", item.ParentIndexNumber.Value.ToString(CultureInfo.InvariantCulture));
|
||||
await writer.WriteElementStringAsync(null, "season", null, item.ParentIndexNumber.Value.ToString(CultureInfo.InvariantCulture)).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WriteStartElement("movie");
|
||||
await writer.WriteStartElementAsync(null, "movie", null);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(item.Name))
|
||||
{
|
||||
writer.WriteElementString("title", item.Name);
|
||||
await writer.WriteElementStringAsync(null, "title", null, item.Name).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(item.OriginalTitle))
|
||||
{
|
||||
writer.WriteElementString("originaltitle", item.OriginalTitle);
|
||||
await writer.WriteElementStringAsync(null, "originaltitle", null, item.OriginalTitle).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (item.PremiereDate.HasValue)
|
||||
{
|
||||
var formatString = options.ReleaseDateFormat;
|
||||
|
||||
writer.WriteElementString(
|
||||
await writer.WriteElementStringAsync(
|
||||
null,
|
||||
"premiered",
|
||||
item.PremiereDate.Value.ToLocalTime().ToString(formatString, CultureInfo.InvariantCulture));
|
||||
writer.WriteElementString(
|
||||
null,
|
||||
item.PremiereDate.Value.ToLocalTime().ToString(formatString, CultureInfo.InvariantCulture)).ConfigureAwait(false);
|
||||
await writer.WriteElementStringAsync(
|
||||
null,
|
||||
"releasedate",
|
||||
item.PremiereDate.Value.ToLocalTime().ToString(formatString, CultureInfo.InvariantCulture));
|
||||
null,
|
||||
item.PremiereDate.Value.ToLocalTime().ToString(formatString, CultureInfo.InvariantCulture)).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
writer.WriteElementString(
|
||||
await writer.WriteElementStringAsync(
|
||||
null,
|
||||
"dateadded",
|
||||
DateTime.Now.ToString(DateAddedFormat, CultureInfo.InvariantCulture));
|
||||
null,
|
||||
DateTime.Now.ToString(DateAddedFormat, CultureInfo.InvariantCulture)).ConfigureAwait(false);
|
||||
|
||||
if (item.ProductionYear.HasValue)
|
||||
{
|
||||
writer.WriteElementString("year", item.ProductionYear.Value.ToString(CultureInfo.InvariantCulture));
|
||||
await writer.WriteElementStringAsync(null, "year", null, item.ProductionYear.Value.ToString(CultureInfo.InvariantCulture)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(item.OfficialRating))
|
||||
{
|
||||
writer.WriteElementString("mpaa", item.OfficialRating);
|
||||
await writer.WriteElementStringAsync(null, "mpaa", null, item.OfficialRating).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
var overview = (item.Overview ?? string.Empty)
|
||||
.StripHtml()
|
||||
.Replace(""", "'", StringComparison.Ordinal);
|
||||
|
||||
writer.WriteElementString("plot", overview);
|
||||
await writer.WriteElementStringAsync(null, "plot", null, overview).ConfigureAwait(false);
|
||||
|
||||
if (item.CommunityRating.HasValue)
|
||||
{
|
||||
writer.WriteElementString("rating", item.CommunityRating.Value.ToString(CultureInfo.InvariantCulture));
|
||||
await writer.WriteElementStringAsync(null, "rating", null, item.CommunityRating.Value.ToString(CultureInfo.InvariantCulture)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
foreach (var genre in item.Genres)
|
||||
{
|
||||
writer.WriteElementString("genre", genre);
|
||||
await writer.WriteElementStringAsync(null, "genre", null, genre).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
var people = item.Id.Equals(Guid.Empty) ? new List<PersonInfo>() : _libraryManager.GetPeople(item);
|
||||
var people = item.Id.Equals(default) ? new List<PersonInfo>() : _libraryManager.GetPeople(item);
|
||||
|
||||
var directors = people
|
||||
.Where(i => IsPersonType(i, PersonType.Director))
|
||||
@@ -2023,7 +2033,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
|
||||
foreach (var person in directors)
|
||||
{
|
||||
writer.WriteElementString("director", person);
|
||||
await writer.WriteElementStringAsync(null, "director", null, person).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
var writers = people
|
||||
@@ -2034,19 +2044,19 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
|
||||
foreach (var person in writers)
|
||||
{
|
||||
writer.WriteElementString("writer", person);
|
||||
await writer.WriteElementStringAsync(null, "writer", null, person).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
foreach (var person in writers)
|
||||
{
|
||||
writer.WriteElementString("credits", person);
|
||||
await writer.WriteElementStringAsync(null, "credits", null, person).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
var tmdbCollection = item.GetProviderId(MetadataProvider.TmdbCollection);
|
||||
|
||||
if (!string.IsNullOrEmpty(tmdbCollection))
|
||||
{
|
||||
writer.WriteElementString("collectionnumber", tmdbCollection);
|
||||
await writer.WriteElementStringAsync(null, "collectionnumber", null, tmdbCollection).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
var imdb = item.GetProviderId(MetadataProvider.Imdb);
|
||||
@@ -2054,10 +2064,10 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
{
|
||||
if (!isSeriesEpisode)
|
||||
{
|
||||
writer.WriteElementString("id", imdb);
|
||||
await writer.WriteElementStringAsync(null, "id", null, imdb).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
writer.WriteElementString("imdbid", imdb);
|
||||
await writer.WriteElementStringAsync(null, "imdbid", null, imdb).ConfigureAwait(false);
|
||||
|
||||
// No need to lock if we have identified the content already
|
||||
lockData = false;
|
||||
@@ -2066,7 +2076,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
var tvdb = item.GetProviderId(MetadataProvider.Tvdb);
|
||||
if (!string.IsNullOrEmpty(tvdb))
|
||||
{
|
||||
writer.WriteElementString("tvdbid", tvdb);
|
||||
await writer.WriteElementStringAsync(null, "tvdbid", null, tvdb).ConfigureAwait(false);
|
||||
|
||||
// No need to lock if we have identified the content already
|
||||
lockData = false;
|
||||
@@ -2075,7 +2085,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
var tmdb = item.GetProviderId(MetadataProvider.Tmdb);
|
||||
if (!string.IsNullOrEmpty(tmdb))
|
||||
{
|
||||
writer.WriteElementString("tmdbid", tmdb);
|
||||
await writer.WriteElementStringAsync(null, "tmdbid", null, tmdb).ConfigureAwait(false);
|
||||
|
||||
// No need to lock if we have identified the content already
|
||||
lockData = false;
|
||||
@@ -2083,26 +2093,26 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
|
||||
if (lockData)
|
||||
{
|
||||
writer.WriteElementString("lockdata", true.ToString(CultureInfo.InvariantCulture).ToLowerInvariant());
|
||||
await writer.WriteElementStringAsync(null, "lockdata", null, "true").ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (item.CriticRating.HasValue)
|
||||
{
|
||||
writer.WriteElementString("criticrating", item.CriticRating.Value.ToString(CultureInfo.InvariantCulture));
|
||||
await writer.WriteElementStringAsync(null, "criticrating", null, item.CriticRating.Value.ToString(CultureInfo.InvariantCulture)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(item.Tagline))
|
||||
{
|
||||
writer.WriteElementString("tagline", item.Tagline);
|
||||
await writer.WriteElementStringAsync(null, "tagline", null, item.Tagline).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
foreach (var studio in item.Studios)
|
||||
{
|
||||
writer.WriteElementString("studio", studio);
|
||||
await writer.WriteElementStringAsync(null, "studio", null, studio).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
writer.WriteEndElement();
|
||||
writer.WriteEndDocument();
|
||||
await writer.WriteEndElementAsync().ConfigureAwait(false);
|
||||
await writer.WriteEndDocumentAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2372,7 +2382,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
{
|
||||
string channelId = seriesTimer.RecordAnyChannel ? null : seriesTimer.ChannelId;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(channelId) && !parent.ChannelId.Equals(Guid.Empty))
|
||||
if (string.IsNullOrWhiteSpace(channelId) && !parent.ChannelId.Equals(default))
|
||||
{
|
||||
if (!tempChannelCache.TryGetValue(parent.ChannelId, out LiveTvChannel channel))
|
||||
{
|
||||
@@ -2431,7 +2441,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
{
|
||||
string channelId = null;
|
||||
|
||||
if (!programInfo.ChannelId.Equals(Guid.Empty))
|
||||
if (!programInfo.ChannelId.Equals(default))
|
||||
{
|
||||
if (!tempChannelCache.TryGetValue(programInfo.ChannelId, out LiveTvChannel channel))
|
||||
{
|
||||
|
||||
@@ -24,18 +24,19 @@ using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
{
|
||||
public class EncodedRecorder : IRecorder
|
||||
public class EncodedRecorder : IRecorder, IDisposable
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly IMediaEncoder _mediaEncoder;
|
||||
private readonly IServerApplicationPaths _appPaths;
|
||||
private readonly TaskCompletionSource<bool> _taskCompletionSource = new TaskCompletionSource<bool>();
|
||||
private readonly TaskCompletionSource<bool> _taskCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
private readonly IServerConfigurationManager _serverConfigurationManager;
|
||||
private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
|
||||
private bool _hasExited;
|
||||
private Stream _logFileStream;
|
||||
private string _targetPath;
|
||||
private Process _process;
|
||||
private bool _disposed = false;
|
||||
|
||||
public EncodedRecorder(
|
||||
ILogger logger,
|
||||
@@ -64,7 +65,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
|
||||
await RecordFromFile(mediaSource, mediaSource.Path, targetFile, onStarted, cancellationTokenSource.Token).ConfigureAwait(false);
|
||||
|
||||
_logger.LogInformation("Recording completed to file {0}", targetFile);
|
||||
_logger.LogInformation("Recording completed to file {Path}", targetFile);
|
||||
}
|
||||
|
||||
private async Task RecordFromFile(MediaSourceInfo mediaSource, string inputFile, string targetFile, Action onStarted, CancellationToken cancellationToken)
|
||||
@@ -114,7 +115,10 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
// Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback
|
||||
_ = StartStreamingLog(_process.StandardError.BaseStream, _logFileStream);
|
||||
|
||||
_logger.LogInformation("ffmpeg recording process started for {0}", _targetPath);
|
||||
_logger.LogInformation("ffmpeg recording process started for {Path}", _targetPath);
|
||||
|
||||
// Block until ffmpeg exits
|
||||
await _taskCompletionSource.Task.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private string GetCommandLineArgs(MediaSourceInfo mediaSource, string inputTempFile, string targetFile)
|
||||
@@ -323,5 +327,35 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
_logger.LogError(ex, "Error reading ffmpeg recording log");
|
||||
}
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
_logFileStream?.Dispose();
|
||||
_process?.Dispose();
|
||||
}
|
||||
|
||||
_logFileStream = null;
|
||||
_process = null;
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using System.Text;
|
||||
|
||||
namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
{
|
||||
@@ -48,12 +49,18 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(info.EpisodeTitle))
|
||||
{
|
||||
var tmpName = name;
|
||||
if (addHyphen)
|
||||
{
|
||||
name += " -";
|
||||
tmpName += " -";
|
||||
}
|
||||
|
||||
name += " " + info.EpisodeTitle;
|
||||
tmpName += " " + info.EpisodeTitle;
|
||||
// Since the filename will be used with file ext. (.mp4, .ts, etc)
|
||||
if (Encoding.UTF8.GetByteCount(tmpName) < 250)
|
||||
{
|
||||
name = tmpName;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (info.IsMovie && info.ProductionYear != null)
|
||||
|
||||
@@ -28,7 +28,7 @@ using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
{
|
||||
public class SchedulesDirect : IListingsProvider
|
||||
public class SchedulesDirect : IListingsProvider, IDisposable
|
||||
{
|
||||
private const string ApiUrl = "https://json.schedulesdirect.org/20141201";
|
||||
|
||||
@@ -39,6 +39,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
private readonly ConcurrentDictionary<string, NameValuePair> _tokens = new ConcurrentDictionary<string, NameValuePair>();
|
||||
private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
|
||||
private DateTime _lastErrorResponse;
|
||||
private bool _disposed = false;
|
||||
|
||||
public SchedulesDirect(
|
||||
ILogger<SchedulesDirect> logger,
|
||||
@@ -58,8 +59,8 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
{
|
||||
var dates = new List<string>();
|
||||
|
||||
var start = new List<DateTime> { startDateUtc, startDateUtc.ToLocalTime() }.Min().Date;
|
||||
var end = new List<DateTime> { endDateUtc, endDateUtc.ToLocalTime() }.Max().Date;
|
||||
var start = new[] { startDateUtc, startDateUtc.ToLocalTime() }.Min().Date;
|
||||
var end = new[] { endDateUtc, endDateUtc.ToLocalTime() }.Max().Date;
|
||||
|
||||
while (start <= end)
|
||||
{
|
||||
@@ -164,12 +165,12 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
|
||||
const double DesiredAspect = 2.0 / 3;
|
||||
|
||||
programEntry.PrimaryImage = GetProgramImage(ApiUrl, imagesWithText, DesiredAspect) ??
|
||||
GetProgramImage(ApiUrl, allImages, DesiredAspect);
|
||||
programEntry.PrimaryImage = GetProgramImage(ApiUrl, imagesWithText, DesiredAspect, token) ??
|
||||
GetProgramImage(ApiUrl, allImages, DesiredAspect, token);
|
||||
|
||||
const double WideAspect = 16.0 / 9;
|
||||
|
||||
programEntry.ThumbImage = GetProgramImage(ApiUrl, imagesWithText, WideAspect);
|
||||
programEntry.ThumbImage = GetProgramImage(ApiUrl, imagesWithText, WideAspect, token);
|
||||
|
||||
// Don't supply the same image twice
|
||||
if (string.Equals(programEntry.PrimaryImage, programEntry.ThumbImage, StringComparison.Ordinal))
|
||||
@@ -177,7 +178,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
programEntry.ThumbImage = null;
|
||||
}
|
||||
|
||||
programEntry.BackdropImage = GetProgramImage(ApiUrl, imagesWithoutText, WideAspect);
|
||||
programEntry.BackdropImage = GetProgramImage(ApiUrl, imagesWithoutText, WideAspect, token);
|
||||
|
||||
// programEntry.bannerImage = GetProgramImage(ApiUrl, data, "Banner", false) ??
|
||||
// GetProgramImage(ApiUrl, data, "Banner-L1", false) ??
|
||||
@@ -398,7 +399,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
return info;
|
||||
}
|
||||
|
||||
private static string GetProgramImage(string apiUrl, IEnumerable<ImageDataDto> images, double desiredAspect)
|
||||
private static string GetProgramImage(string apiUrl, IEnumerable<ImageDataDto> images, double desiredAspect, string token)
|
||||
{
|
||||
var match = images
|
||||
.OrderBy(i => Math.Abs(desiredAspect - GetAspectRatio(i)))
|
||||
@@ -422,7 +423,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
}
|
||||
else
|
||||
{
|
||||
return apiUrl + "/image/" + uri;
|
||||
return apiUrl + "/image/" + uri + "?token=" + token;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -456,6 +457,8 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
IReadOnlyList<string> programIds,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var token = await GetToken(info, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (programIds.Count == 0)
|
||||
{
|
||||
return Array.Empty<ShowImagesDto>();
|
||||
@@ -477,6 +480,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
{
|
||||
Content = new StringContent(str.ToString(), Encoding.UTF8, MediaTypeNames.Application.Json)
|
||||
};
|
||||
message.Headers.TryAddWithoutValidation("token", token);
|
||||
|
||||
try
|
||||
{
|
||||
@@ -822,5 +826,31 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
_tokenSemaphore?.Dispose();
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Extensions;
|
||||
@@ -26,6 +27,8 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
{
|
||||
public class XmlTvListingsProvider : IListingsProvider
|
||||
{
|
||||
private static readonly TimeSpan _maxCacheAge = TimeSpan.FromHours(1);
|
||||
|
||||
private readonly IServerConfigurationManager _config;
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly ILogger<XmlTvListingsProvider> _logger;
|
||||
@@ -69,13 +72,19 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
return UnzipIfNeeded(info.Path, info.Path);
|
||||
}
|
||||
|
||||
string cacheFilename = DateTime.UtcNow.DayOfYear.ToString(CultureInfo.InvariantCulture) + "-" + DateTime.UtcNow.Hour.ToString(CultureInfo.InvariantCulture) + "-" + info.Id + ".xml";
|
||||
string cacheFilename = info.Id + ".xml";
|
||||
string cacheFile = Path.Combine(_config.ApplicationPaths.CachePath, "xmltv", cacheFilename);
|
||||
if (File.Exists(cacheFile))
|
||||
if (File.Exists(cacheFile) && File.GetLastWriteTimeUtc(cacheFile) >= DateTime.UtcNow.Subtract(_maxCacheAge))
|
||||
{
|
||||
return UnzipIfNeeded(info.Path, cacheFile);
|
||||
}
|
||||
|
||||
// Must check if file exists as parent directory may not exist.
|
||||
if (File.Exists(cacheFile))
|
||||
{
|
||||
File.Delete(cacheFile);
|
||||
}
|
||||
|
||||
_logger.LogInformation("Downloading xmltv listings from {Path}", info.Path);
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(cacheFile));
|
||||
@@ -124,7 +133,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
{
|
||||
using (var stream = File.OpenRead(file))
|
||||
{
|
||||
string tempFolder = Path.Combine(_config.ApplicationPaths.TempDirectory, Guid.NewGuid().ToString());
|
||||
string tempFolder = GetTempFolderPath(stream);
|
||||
Directory.CreateDirectory(tempFolder);
|
||||
|
||||
_zipClient.ExtractFirstFileFromGz(stream, tempFolder, "data.xml");
|
||||
@@ -137,7 +146,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
{
|
||||
using (var stream = File.OpenRead(file))
|
||||
{
|
||||
string tempFolder = Path.Combine(_config.ApplicationPaths.TempDirectory, Guid.NewGuid().ToString());
|
||||
string tempFolder = GetTempFolderPath(stream);
|
||||
Directory.CreateDirectory(tempFolder);
|
||||
|
||||
_zipClient.ExtractAllFromGz(stream, tempFolder, true);
|
||||
@@ -146,6 +155,16 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
}
|
||||
}
|
||||
|
||||
private string GetTempFolderPath(Stream stream)
|
||||
{
|
||||
#pragma warning disable CA5351
|
||||
using var md5 = MD5.Create();
|
||||
#pragma warning restore CA5351
|
||||
var checksum = Convert.ToHexString(md5.ComputeHash(stream));
|
||||
stream.Position = 0;
|
||||
return Path.Combine(_config.ApplicationPaths.TempDirectory, checksum);
|
||||
}
|
||||
|
||||
private string FindXmlFile(string directory)
|
||||
{
|
||||
return _fileSystem.GetFiles(directory, true)
|
||||
|
||||
@@ -176,7 +176,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
try
|
||||
{
|
||||
dto.ParentThumbImageTag = _imageProcessor.GetImageCacheTag(librarySeries, image);
|
||||
dto.ParentThumbItemId = librarySeries.Id.ToString("N", CultureInfo.InvariantCulture);
|
||||
dto.ParentThumbItemId = librarySeries.Id;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -193,7 +193,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
{
|
||||
_imageProcessor.GetImageCacheTag(librarySeries, image)
|
||||
};
|
||||
dto.ParentBackdropItemId = librarySeries.Id.ToString("N", CultureInfo.InvariantCulture);
|
||||
dto.ParentBackdropItemId = librarySeries.Id;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -240,7 +240,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
_imageProcessor.GetImageCacheTag(program, image)
|
||||
};
|
||||
|
||||
dto.ParentBackdropItemId = program.Id.ToString("N", CultureInfo.InvariantCulture);
|
||||
dto.ParentBackdropItemId = program.Id;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -456,7 +456,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
info.Id = timer.ExternalId;
|
||||
}
|
||||
|
||||
if (!dto.ChannelId.Equals(Guid.Empty) && string.IsNullOrEmpty(info.ChannelId))
|
||||
if (!dto.ChannelId.Equals(default) && string.IsNullOrEmpty(info.ChannelId))
|
||||
{
|
||||
var channel = _libraryManager.GetItemById(dto.ChannelId);
|
||||
|
||||
@@ -522,7 +522,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
info.Id = timer.ExternalId;
|
||||
}
|
||||
|
||||
if (!dto.ChannelId.Equals(Guid.Empty) && string.IsNullOrEmpty(info.ChannelId))
|
||||
if (!dto.ChannelId.Equals(default) && string.IsNullOrEmpty(info.ChannelId))
|
||||
{
|
||||
var channel = _libraryManager.GetItemById(dto.ChannelId);
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
/// <summary>
|
||||
/// Class LiveTvManager.
|
||||
/// </summary>
|
||||
public class LiveTvManager : ILiveTvManager, IDisposable
|
||||
public class LiveTvManager : ILiveTvManager
|
||||
{
|
||||
private const int MaxGuideDays = 14;
|
||||
private const string ExternalServiceTag = "ExternalServiceId";
|
||||
@@ -63,8 +63,6 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
private ITunerHost[] _tunerHosts = Array.Empty<ITunerHost>();
|
||||
private IListingsProvider[] _listingProviders = Array.Empty<IListingsProvider>();
|
||||
|
||||
private bool _disposed = false;
|
||||
|
||||
public LiveTvManager(
|
||||
IServerConfigurationManager config,
|
||||
ILogger<LiveTvManager> logger,
|
||||
@@ -178,7 +176,9 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
|
||||
public QueryResult<BaseItem> GetInternalChannels(LiveTvChannelQuery query, DtoOptions dtoOptions, CancellationToken cancellationToken)
|
||||
{
|
||||
var user = query.UserId == Guid.Empty ? null : _userManager.GetUserById(query.UserId);
|
||||
var user = query.UserId.Equals(default)
|
||||
? null
|
||||
: _userManager.GetUserById(query.UserId);
|
||||
|
||||
var topFolder = GetInternalLiveTvFolder(cancellationToken);
|
||||
|
||||
@@ -312,7 +312,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
{
|
||||
if (isVideo)
|
||||
{
|
||||
mediaSource.MediaStreams.AddRange(new List<MediaStream>
|
||||
mediaSource.MediaStreams = new MediaStream[]
|
||||
{
|
||||
new MediaStream
|
||||
{
|
||||
@@ -329,11 +329,11 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
// Set the index to -1 because we don't know the exact index of the audio stream within the container
|
||||
Index = -1
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
mediaSource.MediaStreams.AddRange(new List<MediaStream>
|
||||
mediaSource.MediaStreams = new MediaStream[]
|
||||
{
|
||||
new MediaStream
|
||||
{
|
||||
@@ -341,7 +341,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
// Set the index to -1 because we don't know the exact index of the audio stream within the container
|
||||
Index = -1
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -857,11 +857,10 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
|
||||
var returnArray = _dtoService.GetBaseItemDtos(queryResult.Items, options, user);
|
||||
|
||||
return new QueryResult<BaseItemDto>
|
||||
{
|
||||
Items = returnArray,
|
||||
TotalRecordCount = queryResult.TotalRecordCount
|
||||
};
|
||||
return new QueryResult<BaseItemDto>(
|
||||
query.StartIndex,
|
||||
queryResult.TotalRecordCount,
|
||||
returnArray);
|
||||
}
|
||||
|
||||
public QueryResult<BaseItem> GetRecommendedProgramsInternal(InternalItemsQuery query, DtoOptions options, CancellationToken cancellationToken)
|
||||
@@ -910,29 +909,27 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
programs = programs.Take(query.Limit.Value);
|
||||
}
|
||||
|
||||
return new QueryResult<BaseItem>
|
||||
{
|
||||
Items = programs.ToArray(),
|
||||
TotalRecordCount = totalCount
|
||||
};
|
||||
return new QueryResult<BaseItem>(
|
||||
query.StartIndex,
|
||||
totalCount,
|
||||
programs.ToArray());
|
||||
}
|
||||
|
||||
public QueryResult<BaseItemDto> GetRecommendedPrograms(InternalItemsQuery query, DtoOptions options, CancellationToken cancellationToken)
|
||||
public Task<QueryResult<BaseItemDto>> GetRecommendedProgramsAsync(InternalItemsQuery query, DtoOptions options, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!(query.IsAiring ?? false))
|
||||
{
|
||||
return GetPrograms(query, options, cancellationToken).Result;
|
||||
return GetPrograms(query, options, cancellationToken);
|
||||
}
|
||||
|
||||
RemoveFields(options);
|
||||
|
||||
var internalResult = GetRecommendedProgramsInternal(query, options, cancellationToken);
|
||||
|
||||
return new QueryResult<BaseItemDto>
|
||||
{
|
||||
Items = _dtoService.GetBaseItemDtos(internalResult.Items, options, query.User),
|
||||
TotalRecordCount = internalResult.TotalRecordCount
|
||||
};
|
||||
return Task.FromResult(new QueryResult<BaseItemDto>(
|
||||
query.StartIndex,
|
||||
internalResult.TotalRecordCount,
|
||||
_dtoService.GetBaseItemDtos(internalResult.Items, options, query.User)));
|
||||
}
|
||||
|
||||
private int GetRecommendationScore(LiveTvProgram program, User user, bool factorChannelWatchCount)
|
||||
@@ -1273,7 +1270,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
if (itemId.Equals(Guid.Empty))
|
||||
if (itemId.Equals(default))
|
||||
{
|
||||
// Somehow some invalid data got into the db. It probably predates the boundary checking
|
||||
continue;
|
||||
@@ -1533,7 +1530,9 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
|
||||
public QueryResult<BaseItemDto> GetRecordings(RecordingQuery query, DtoOptions options)
|
||||
{
|
||||
var user = query.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(query.UserId);
|
||||
var user = query.UserId.Equals(default)
|
||||
? null
|
||||
: _userManager.GetUserById(query.UserId);
|
||||
|
||||
RemoveFields(options);
|
||||
|
||||
@@ -1541,11 +1540,10 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
|
||||
var returnArray = _dtoService.GetBaseItemDtos(internalResult.Items, options, user);
|
||||
|
||||
return new QueryResult<BaseItemDto>
|
||||
{
|
||||
Items = returnArray,
|
||||
TotalRecordCount = internalResult.TotalRecordCount
|
||||
};
|
||||
return new QueryResult<BaseItemDto>(
|
||||
query.StartIndex,
|
||||
internalResult.TotalRecordCount,
|
||||
returnArray);
|
||||
}
|
||||
|
||||
private async Task<QueryResult<TimerInfo>> GetTimersInternal(TimerQuery query, CancellationToken cancellationToken)
|
||||
@@ -1593,7 +1591,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
if (!string.IsNullOrEmpty(query.ChannelId))
|
||||
{
|
||||
var guid = new Guid(query.ChannelId);
|
||||
timers = timers.Where(i => guid == _tvDtoService.GetInternalChannelId(i.Item2.Name, i.Item1.ChannelId));
|
||||
timers = timers.Where(i => _tvDtoService.GetInternalChannelId(i.Item2.Name, i.Item1.ChannelId).Equals(guid));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(query.SeriesTimerId))
|
||||
@@ -1601,7 +1599,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
var guid = new Guid(query.SeriesTimerId);
|
||||
|
||||
timers = timers
|
||||
.Where(i => _tvDtoService.GetInternalSeriesTimerId(i.Item1.SeriesTimerId) == guid);
|
||||
.Where(i => _tvDtoService.GetInternalSeriesTimerId(i.Item1.SeriesTimerId).Equals(guid));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(query.Id))
|
||||
@@ -1615,11 +1613,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
.OrderBy(i => i.StartDate)
|
||||
.ToArray();
|
||||
|
||||
return new QueryResult<TimerInfo>
|
||||
{
|
||||
Items = returnArray,
|
||||
TotalRecordCount = returnArray.Length
|
||||
};
|
||||
return new QueryResult<TimerInfo>(returnArray);
|
||||
}
|
||||
|
||||
public async Task<QueryResult<TimerInfoDto>> GetTimers(TimerQuery query, CancellationToken cancellationToken)
|
||||
@@ -1667,7 +1661,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
if (!string.IsNullOrEmpty(query.ChannelId))
|
||||
{
|
||||
var guid = new Guid(query.ChannelId);
|
||||
timers = timers.Where(i => guid == _tvDtoService.GetInternalChannelId(i.Item2.Name, i.Item1.ChannelId));
|
||||
timers = timers.Where(i => _tvDtoService.GetInternalChannelId(i.Item2.Name, i.Item1.ChannelId).Equals(guid));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(query.SeriesTimerId))
|
||||
@@ -1675,7 +1669,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
var guid = new Guid(query.SeriesTimerId);
|
||||
|
||||
timers = timers
|
||||
.Where(i => _tvDtoService.GetInternalSeriesTimerId(i.Item1.SeriesTimerId) == guid);
|
||||
.Where(i => _tvDtoService.GetInternalSeriesTimerId(i.Item1.SeriesTimerId).Equals(guid));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(query.Id))
|
||||
@@ -1701,11 +1695,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
.OrderBy(i => i.StartDate)
|
||||
.ToArray();
|
||||
|
||||
return new QueryResult<TimerInfoDto>
|
||||
{
|
||||
Items = returnArray,
|
||||
TotalRecordCount = returnArray.Length
|
||||
};
|
||||
return new QueryResult<TimerInfoDto>(returnArray);
|
||||
}
|
||||
|
||||
public async Task CancelTimer(string id)
|
||||
@@ -1801,11 +1791,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
.Select(i => i.Item1)
|
||||
.ToArray();
|
||||
|
||||
return new QueryResult<SeriesTimerInfo>
|
||||
{
|
||||
Items = returnArray,
|
||||
TotalRecordCount = returnArray.Length
|
||||
};
|
||||
return new QueryResult<SeriesTimerInfo>(returnArray);
|
||||
}
|
||||
|
||||
public async Task<QueryResult<SeriesTimerInfoDto>> GetSeriesTimers(SeriesTimerQuery query, CancellationToken cancellationToken)
|
||||
@@ -1855,11 +1841,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
})
|
||||
.ToArray();
|
||||
|
||||
return new QueryResult<SeriesTimerInfoDto>
|
||||
{
|
||||
Items = returnArray,
|
||||
TotalRecordCount = returnArray.Length
|
||||
};
|
||||
return new QueryResult<SeriesTimerInfoDto>(returnArray);
|
||||
}
|
||||
|
||||
public BaseItem GetLiveTvChannel(TimerInfo timer, ILiveTvService service)
|
||||
@@ -2112,36 +2094,6 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases unmanaged and - optionally - managed resources.
|
||||
/// </summary>
|
||||
/// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
|
||||
protected virtual void Dispose(bool dispose)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (dispose)
|
||||
{
|
||||
// TODO: Dispose stuff
|
||||
}
|
||||
|
||||
_services = null;
|
||||
_listingProviders = null;
|
||||
_tunerHosts = null;
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
private LiveTvServiceInfo[] GetServiceInfos()
|
||||
{
|
||||
return Services.Select(GetServiceInfo).ToArray();
|
||||
|
||||
@@ -50,7 +50,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
public string Key => "RefreshGuide";
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task Execute(CancellationToken cancellationToken, IProgress<double> progress)
|
||||
public Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
|
||||
{
|
||||
var manager = (LiveTvManager)_liveTvManager;
|
||||
|
||||
|
||||
@@ -446,7 +446,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
{
|
||||
Path = url,
|
||||
Protocol = MediaProtocol.Udp,
|
||||
MediaStreams = new List<MediaStream>
|
||||
MediaStreams = new MediaStream[]
|
||||
{
|
||||
new MediaStream
|
||||
{
|
||||
|
||||
@@ -67,7 +67,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
|
||||
int receivedBytes = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return VerifyReturnValueOfGetSet(buffer.AsSpan(receivedBytes), "none");
|
||||
return VerifyReturnValueOfGetSet(buffer.AsSpan(0, receivedBytes), "none");
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -80,7 +80,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
_remoteEndPoint = new IPEndPoint(remoteIp, HdHomeRunPort);
|
||||
|
||||
_tcpClient = new TcpClient();
|
||||
_tcpClient.Connect(_remoteEndPoint);
|
||||
await _tcpClient.ConnectAsync(_remoteEndPoint, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (!_lockkey.HasValue)
|
||||
{
|
||||
@@ -158,7 +158,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
}
|
||||
|
||||
using var tcpClient = new TcpClient();
|
||||
tcpClient.Connect(_remoteEndPoint);
|
||||
await tcpClient.ConnectAsync(_remoteEndPoint, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
using var stream = tcpClient.GetStream();
|
||||
var commandList = commands.GetCommands();
|
||||
|
||||
@@ -133,7 +133,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
}
|
||||
}
|
||||
|
||||
var taskCompletionSource = new TaskCompletionSource<bool>();
|
||||
var taskCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
|
||||
_ = StartStreaming(
|
||||
udpClient,
|
||||
@@ -165,7 +165,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
{
|
||||
await CopyTo(udpClient, TempFilePath, openTaskCompletionSource, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException ex)
|
||||
catch (Exception ex) when (ex is OperationCanceledException || ex is TimeoutException)
|
||||
{
|
||||
Logger.LogInformation("HDHR UDP stream cancelled or timed out from {0}", remoteAddress);
|
||||
openTaskCompletionSource.TrySetException(ex);
|
||||
@@ -186,41 +186,30 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
{
|
||||
var resolved = false;
|
||||
|
||||
using (var fileStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.Read))
|
||||
var fileStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.Read);
|
||||
await using (fileStream.ConfigureAwait(false))
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
using (var timeOutSource = new CancellationTokenSource())
|
||||
using (var linkedSource = CancellationTokenSource.CreateLinkedTokenSource(
|
||||
cancellationToken,
|
||||
timeOutSource.Token))
|
||||
var res = await udpClient.ReceiveAsync(cancellationToken)
|
||||
.AsTask()
|
||||
.WaitAsync(TimeSpan.FromMilliseconds(30000), CancellationToken.None)
|
||||
.ConfigureAwait(false);
|
||||
var buffer = res.Buffer;
|
||||
|
||||
var read = buffer.Length - RtpHeaderBytes;
|
||||
|
||||
if (read > 0)
|
||||
{
|
||||
var resTask = udpClient.ReceiveAsync(linkedSource.Token).AsTask();
|
||||
if (await Task.WhenAny(resTask, Task.Delay(30000, linkedSource.Token)).ConfigureAwait(false) != resTask)
|
||||
{
|
||||
resTask.Dispose();
|
||||
break;
|
||||
}
|
||||
await fileStream.WriteAsync(buffer.AsMemory(RtpHeaderBytes, read), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
// We don't want all these delay tasks to keep running
|
||||
timeOutSource.Cancel();
|
||||
var res = await resTask.ConfigureAwait(false);
|
||||
var buffer = res.Buffer;
|
||||
|
||||
var read = buffer.Length - RtpHeaderBytes;
|
||||
|
||||
if (read > 0)
|
||||
{
|
||||
await fileStream.WriteAsync(buffer.AsMemory(RtpHeaderBytes, read), linkedSource.Token).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (!resolved)
|
||||
{
|
||||
resolved = true;
|
||||
DateOpened = DateTime.UtcNow;
|
||||
openTaskCompletionSource.TrySetResult(true);
|
||||
}
|
||||
if (!resolved)
|
||||
{
|
||||
resolved = true;
|
||||
DateOpened = DateTime.UtcNow;
|
||||
openTaskCompletionSource.TrySetResult(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,7 +97,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
|
||||
public Stream GetStream()
|
||||
{
|
||||
var stream = GetInputStream(TempFilePath);
|
||||
var stream = new FileStream(
|
||||
TempFilePath,
|
||||
FileMode.Open,
|
||||
FileAccess.Read,
|
||||
FileShare.ReadWrite,
|
||||
IODefaults.FileStreamBufferSize,
|
||||
FileOptions.SequentialScan | FileOptions.Asynchronous);
|
||||
|
||||
bool seekFile = (DateTime.UtcNow - DateOpened).TotalSeconds > 10;
|
||||
if (seekFile)
|
||||
{
|
||||
@@ -107,15 +114,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
return stream;
|
||||
}
|
||||
|
||||
protected FileStream GetInputStream(string path)
|
||||
=> new FileStream(
|
||||
path,
|
||||
FileMode.Open,
|
||||
FileAccess.Read,
|
||||
FileShare.ReadWrite,
|
||||
IODefaults.FileStreamBufferSize,
|
||||
FileOptions.SequentialScan | FileOptions.Asynchronous);
|
||||
|
||||
protected async Task DeleteTempFiles(string path, int retryCount = 0)
|
||||
{
|
||||
if (retryCount == 0)
|
||||
|
||||
@@ -170,7 +170,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
{
|
||||
Path = path,
|
||||
Protocol = protocol,
|
||||
MediaStreams = new List<MediaStream>
|
||||
MediaStreams = new MediaStream[]
|
||||
{
|
||||
new MediaStream
|
||||
{
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
#nullable disable
|
||||
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
@@ -51,7 +49,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
|
||||
var url = mediaSource.Path;
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(TempFilePath));
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(TempFilePath) ?? throw new InvalidOperationException("Path can't be a root directory."));
|
||||
|
||||
var typeName = GetType().Name;
|
||||
Logger.LogInformation("Opening {StreamType} Live stream from {Url}", typeName, url);
|
||||
@@ -75,7 +73,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
|
||||
SetTempFilePath("ts");
|
||||
|
||||
var taskCompletionSource = new TaskCompletionSource<bool>();
|
||||
var taskCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
|
||||
_ = StartStreaming(response, taskCompletionSource, LiveStreamCancellationTokenSource.Token);
|
||||
|
||||
@@ -94,14 +92,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
// OpenedMediaSource.SupportsDirectPlay = false;
|
||||
// OpenedMediaSource.SupportsDirectStream = true;
|
||||
// OpenedMediaSource.SupportsTranscoding = true;
|
||||
await taskCompletionSource.Task.ConfigureAwait(false);
|
||||
if (taskCompletionSource.Task.Exception != null)
|
||||
{
|
||||
// Error happened while opening the stream so raise the exception again to inform the caller
|
||||
throw taskCompletionSource.Task.Exception;
|
||||
}
|
||||
|
||||
if (!taskCompletionSource.Task.Result)
|
||||
var res = await taskCompletionSource.Task.ConfigureAwait(false);
|
||||
if (!res)
|
||||
{
|
||||
Logger.LogWarning("Zero bytes copied from stream {StreamType} to {FilePath} but no exception raised", GetType().Name, TempFilePath);
|
||||
throw new EndOfStreamException(string.Format(CultureInfo.InvariantCulture, "Zero bytes copied from stream {0}", GetType().Name));
|
||||
|
||||
@@ -120,5 +120,8 @@
|
||||
"Forced": "Forçat",
|
||||
"Default": "Defecto",
|
||||
"TaskOptimizeDatabaseDescription": "Compacta la base de dades i trunca l'espai lliure. Executar aquesta tasca després d’escanejar la biblioteca o fer altres canvis que impliquin modificacions a la base de dades pot millorar el rendiment.",
|
||||
"TaskOptimizeDatabase": "Optimitzar la base de dades"
|
||||
"TaskOptimizeDatabase": "Optimitzar la base de dades",
|
||||
"TaskKeyframeExtractorDescription": "Extreu fotogrames clau dels fitxers de vídeo per crear llistes de reproducció HLS més precises. Aquesta tasca pot durar molt de temps.",
|
||||
"TaskKeyframeExtractor": "Extractor de fotogrames clau",
|
||||
"External": "Extern"
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
"HeaderFavoriteShows": "Oblíbené seriály",
|
||||
"HeaderFavoriteSongs": "Oblíbená hudba",
|
||||
"HeaderLiveTV": "Televize",
|
||||
"HeaderNextUp": "Nadcházející",
|
||||
"HeaderNextUp": "Další díly",
|
||||
"HeaderRecordingGroups": "Skupiny nahrávek",
|
||||
"HomeVideos": "Domácí videa",
|
||||
"Inherit": "Zdědit",
|
||||
@@ -77,7 +77,7 @@
|
||||
"SubtitleDownloadFailureFromForItem": "Stažení titulků pro {1} z {0} selhalo",
|
||||
"Sync": "Synchronizace",
|
||||
"System": "Systém",
|
||||
"TvShows": "TV seriály",
|
||||
"TvShows": "Seriály",
|
||||
"User": "Uživatel",
|
||||
"UserCreatedWithName": "Uživatel {0} byl vytvořen",
|
||||
"UserDeletedWithName": "Uživatel {0} byl smazán",
|
||||
@@ -120,5 +120,8 @@
|
||||
"Forced": "Vynucené",
|
||||
"Default": "Výchozí",
|
||||
"TaskOptimizeDatabaseDescription": "Zmenší databázi a odstraní prázdné místo. Spuštění této úlohy po skenování knihovny či jiných změnách databáze může zlepšit výkon.",
|
||||
"TaskOptimizeDatabase": "Optimalizovat databázi"
|
||||
"TaskOptimizeDatabase": "Optimalizovat databázi",
|
||||
"TaskKeyframeExtractorDescription": "Vytahuje klíčové snímky ze souborů videa za účelem vytváření přesnějších seznamů přehrávání HLS. Tento úkol může trvat velmi dlouho.",
|
||||
"TaskKeyframeExtractor": "Vytahovač klíčových snímků",
|
||||
"External": "Externí"
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
"Folders": "Ffolderi",
|
||||
"Favorites": "Ffefrynnau",
|
||||
"LabelRunningTimeValue": "Amser rhedeg: {0}",
|
||||
"TaskOptimizeDatabase": "Cronfa ddata Optimeiddio",
|
||||
"TaskOptimizeDatabase": "Optimeiddio cronfa ddata",
|
||||
"TaskRefreshChannels": "Adnewyddu Sianeli",
|
||||
"TaskRefreshPeople": "Adnewyddu Pobl",
|
||||
"TasksChannelsCategory": "Sianeli Internet",
|
||||
@@ -54,5 +54,73 @@
|
||||
"Undefined": "Heb ddiffiniad",
|
||||
"TvShows": "Rhaglenni teledu",
|
||||
"HeaderLiveTV": "Teledu Byw",
|
||||
"User": "Defnyddiwr"
|
||||
"User": "Defnyddiwr",
|
||||
"TaskCleanLogsDescription": "Dileu ffeiliau log sy'n fwy na {0} diwrnod oed.",
|
||||
"TaskCleanLogs": "Glanhau ffolder log",
|
||||
"TaskRefreshLibraryDescription": "Sganio'ch llyfrgell gyfryngau am ffeiliau newydd ac yn adnewyddu metaddata.",
|
||||
"TaskRefreshLibrary": "Sganwich Llyfrgell Cyfryngau",
|
||||
"TaskCleanActivityLogDescription": "Yn dileu cofnodion log gweithgaredd sy'n hŷn na'r oedran a nodwyd.",
|
||||
"TaskCleanActivityLog": "Glanhau Log Gweithgaredd",
|
||||
"SubtitleDownloadFailureFromForItem": "Methodd is-deitlau lawrlwytho o {0} ar gyfer {1}",
|
||||
"NotificationOptionPluginError": "Methodd ategyn",
|
||||
"NotificationOptionAudioPlaybackStopped": "Stopiwyd chwarae sain",
|
||||
"NotificationOptionAudioPlayback": "Dechreuwyd chwarae sain",
|
||||
"MessageServerConfigurationUpdated": "Mae gosodiadau gweinydd wedi'i ddiweddaru",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "Mae adran gosodiadau gweinydd {0} wedi'i diweddaru",
|
||||
"FailedLoginAttemptWithUserName": "Cais mewngofnodi wedi methu gan {0}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} wedi'i hychwanegu at eich llyfrgell gyfryngau",
|
||||
"UserStoppedPlayingItemWithValues": "{0} wedi gorffen chwarae {1} ar {2}",
|
||||
"UserStartedPlayingItemWithValues": "{0} yn chwarae {1} ar {2}",
|
||||
"UserPolicyUpdatedWithName": "Polisi defnyddiwr wedi'i newid ar gyfer {0}",
|
||||
"UserPasswordChangedWithName": "Cyfrinair wedi'i newid ar gyfer defnyddiwr {0}",
|
||||
"UserOnlineFromDevice": "Mae {0} ar-lein o {1}",
|
||||
"UserOfflineFromDevice": "Mae {0} wedi datgysylltu o {1}",
|
||||
"UserLockedOutWithName": "Mae defnyddiwr {0} wedi'i gloi allan",
|
||||
"UserDownloadingItemWithValues": "Mae {0} yn lawrlwytho {1}",
|
||||
"UserDeletedWithName": "Defnyddiwr {0} wedi'i ddileu",
|
||||
"UserCreatedWithName": "Defnyddiwr {0} wedi'i greu",
|
||||
"StartupEmbyServerIsLoading": "Gweinydd Jellyfin yn llwytho. Triwch eto mewn ychydig.",
|
||||
"ServerNameNeedsToBeRestarted": "Mae angen ailddechrau {0}",
|
||||
"PluginUpdatedWithName": "{0} wedi'i ddiweddaru",
|
||||
"PluginUninstalledWithName": "{0} wedi'i ddadosod",
|
||||
"PluginInstalledWithName": "{0} wedi'i osod",
|
||||
"NotificationOptionVideoPlaybackStopped": "Stopiwyd chwarae fideo",
|
||||
"NotificationOptionVideoPlayback": "Dechreuwyd chwarae fideo",
|
||||
"NotificationOptionUserLockedOut": "Defnyddiwr wedi'i gloi allan",
|
||||
"NotificationOptionTaskFailed": "Methwyd cyflawni y dasg a drefnwyd",
|
||||
"NotificationOptionServerRestartRequired": "Mae angen ailgychwyn y gweinydd",
|
||||
"NotificationOptionPluginUpdateInstalled": "Diweddariad ategyn wedi'i osod",
|
||||
"NotificationOptionPluginUninstalled": "Ategyn wedi'i ddadosod",
|
||||
"NotificationOptionPluginInstalled": "Ategyn wedi'i osod",
|
||||
"NotificationOptionNewLibraryContent": "Cynnwys newydd ar gael",
|
||||
"NotificationOptionCameraImageUploaded": "Llun camera wedi'i huwchlwytho",
|
||||
"NotificationOptionApplicationUpdateInstalled": "Diweddariad ap wedi'i osod",
|
||||
"NotificationOptionApplicationUpdateAvailable": "Diweddariad ap ar gael",
|
||||
"NewVersionIsAvailable": "Mae fersiwn diweddarach o'r gweinydd Jellyfin ar gael.",
|
||||
"NameInstallFailed": "Gosodiad {0} wedi methu",
|
||||
"MessageApplicationUpdatedTo": "Gweinydd Jellyfin wedi'i ddiweddaru i {0}",
|
||||
"MessageApplicationUpdated": "Gweinydd Jellyfin wedi'i ddiweddaru",
|
||||
"LabelIpAddressValue": "Cyfeiriad IP: {0}",
|
||||
"ItemRemovedWithName": "{0} wedi'i dynnu o'r llyfrgell",
|
||||
"ItemAddedWithName": "{0} wedi'i adio i'r llyfrgell",
|
||||
"HeaderRecordingGroups": "Grwpiau Recordio",
|
||||
"HeaderFavoriteSongs": "Ffefryn Ganeuon",
|
||||
"HeaderFavoriteShows": "Ffefryn Shoeau",
|
||||
"HeaderFavoriteEpisodes": "Ffefryn Rhaglenni",
|
||||
"TaskDownloadMissingSubtitlesDescription": "Chwilio'r rhyngrwyd am is-deitlau coll yn seiliedig ar gosodiadau metaddata.",
|
||||
"TaskDownloadMissingSubtitles": "Lawrlwytho isdeitlau coll",
|
||||
"TaskCleanTranscodeDescription": "Dileu ffeiliau trawsgodio fwy nag un diwrnod oed.",
|
||||
"External": "Allanol",
|
||||
"TaskKeyframeExtractorDescription": "Echdynnu fframiau o ffeiliau fideo i greu rhestrau chwarae HLS mwy manwl gywir. Gall y dasg hon redeg am amser hir.",
|
||||
"TaskKeyframeExtractor": "Echdynnwr ffram-allwedd",
|
||||
"TaskOptimizeDatabaseDescription": "Crynhoi cronfa ddata ac yn cwtogi'r gofod rhydd. Gallai rhedeg y dasg hon ar ôl sganio'r llyfrgell neu wneud newidiadau eraill sy'n addasu'r cronfa ddata wella perfformiad.",
|
||||
"TaskRefreshChannelsDescription": "Diweddaru gwybodaeth sianeli rhyngrwyd.",
|
||||
"TaskCleanTranscode": "Gwaghau Ffolder Trawsgodau",
|
||||
"TaskUpdatePluginsDescription": "Lawrlwytho ac yn gosod diweddariadau ar gyfer ategion sydd wedi'u gosod i'w diweddaru'n awtomatig.",
|
||||
"TaskUpdatePlugins": "Diweddaru Ategion",
|
||||
"TaskRefreshPeopleDescription": "Yn diweddaru metaddata ar gyfer actorion a chyfarwyddwyr yn eich llyfrgell gyfryngau.",
|
||||
"TaskRefreshChapterImagesDescription": "Creu mân-luniau ar gyfer fideos sydd â phenodau.",
|
||||
"TaskRefreshChapterImages": "Echdynnu Lluniau Pennod",
|
||||
"TaskCleanCacheDescription": "Dileu ffeiliau cache nad oes eu hangen ar y system mwyach.",
|
||||
"TaskCleanCache": "Gwaghau Ffolder Cache"
|
||||
}
|
||||
|
||||
@@ -120,5 +120,8 @@
|
||||
"Forced": "Erzwungen",
|
||||
"Default": "Standard",
|
||||
"TaskOptimizeDatabaseDescription": "Komprimiert die Datenbank und trimmt den freien Speicherplatz. Die Ausführung dieser Aufgabe nach dem Scannen der Bibliothek oder nach anderen Änderungen, die Datenbankänderungen implizieren, kann die Leistung verbessern.",
|
||||
"TaskOptimizeDatabase": "Datenbank optimieren"
|
||||
"TaskOptimizeDatabase": "Datenbank optimieren",
|
||||
"TaskKeyframeExtractorDescription": "Extrahiere Keyframes aus Videodateien, um präzisere HLS-Playlisten zu erzeugen. Diese Aufgabe kann sehr lange dauern.",
|
||||
"TaskKeyframeExtractor": "Keyframe Extraktor",
|
||||
"External": "Extern"
|
||||
}
|
||||
|
||||
@@ -120,5 +120,8 @@
|
||||
"Forced": "Εξαναγκασμένο",
|
||||
"Default": "Προεπιλογή",
|
||||
"TaskOptimizeDatabaseDescription": "Συμπιέζει τη βάση δεδομένων και δημιουργεί ελεύθερο χώρο. Η εκτέλεση αυτής της εργασίας μετά τη σάρωση της βιβλιοθήκης ή την πραγματοποίηση άλλων αλλαγών που συνεπάγονται τροποποιήσεις της βάσης δεδομένων μπορεί να βελτιώσει την απόδοση.",
|
||||
"TaskOptimizeDatabase": "Βελτιστοποίηση βάσης δεδομένων"
|
||||
"TaskOptimizeDatabase": "Βελτιστοποίηση βάσης δεδομένων",
|
||||
"TaskKeyframeExtractorDescription": "Εξάγει τα βασικά καρέ από αρχεία βίντεο για να δημιουργήσει πιο ακριβείς HLS λίστες αναπαραγωγής. Αυτή η εργασία μπορεί να διαρκέσει πολλή ώρα.",
|
||||
"TaskKeyframeExtractor": "Εξαγωγέας βασικών καρέ βίντεο",
|
||||
"External": "Εξωτερικό"
|
||||
}
|
||||
|
||||
@@ -120,5 +120,8 @@
|
||||
"Forced": "Forced",
|
||||
"Default": "Default",
|
||||
"TaskOptimizeDatabaseDescription": "Compacts database and truncates free space. Running this task after scanning the library or doing other changes that imply database modifications might improve performance.",
|
||||
"TaskOptimizeDatabase": "Optimise database"
|
||||
"TaskOptimizeDatabase": "Optimise database",
|
||||
"TaskKeyframeExtractorDescription": "Extracts keyframes from video files to create more precise HLS playlists. This task may run for a long time.",
|
||||
"TaskKeyframeExtractor": "Keyframe Extractor",
|
||||
"External": "External"
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"Default": "Default",
|
||||
"DeviceOfflineWithName": "{0} has disconnected",
|
||||
"DeviceOnlineWithName": "{0} is connected",
|
||||
"External": "External",
|
||||
"FailedLoginAttemptWithUserName": "Failed login try from {0}",
|
||||
"Favorites": "Favorites",
|
||||
"Folders": "Folders",
|
||||
@@ -119,5 +120,7 @@
|
||||
"TaskDownloadMissingSubtitles": "Download missing subtitles",
|
||||
"TaskDownloadMissingSubtitlesDescription": "Searches the internet for missing subtitles based on metadata configuration.",
|
||||
"TaskOptimizeDatabase": "Optimize database",
|
||||
"TaskOptimizeDatabaseDescription": "Compacts database and truncates free space. Running this task after scanning the library or doing other changes that imply database modifications might improve performance."
|
||||
"TaskOptimizeDatabaseDescription": "Compacts database and truncates free space. Running this task after scanning the library or doing other changes that imply database modifications might improve performance.",
|
||||
"TaskKeyframeExtractor": "Keyframe Extractor",
|
||||
"TaskKeyframeExtractorDescription": "Extracts keyframes from video files to create more precise HLS playlists. This task may run for a long time."
|
||||
}
|
||||
|
||||
@@ -119,5 +119,8 @@
|
||||
"HeaderRecordingGroups": "Rikordadaj Grupoj",
|
||||
"FailedLoginAttemptWithUserName": "Malsukcesa ensaluta provo de {0}",
|
||||
"CameraImageUploadedFrom": "Nova kamera bildo estis alŝutita de {0}",
|
||||
"AuthenticationSucceededWithUserName": "{0} sukcese aŭtentikigis"
|
||||
"AuthenticationSucceededWithUserName": "{0} sukcese aŭtentikigis",
|
||||
"TaskKeyframeExtractorDescription": "Eltiras ĉefkadrojn el videodosieroj por krei pli precizajn HLS-ludlistojn. Ĉi tiu tasko povas funkcii dum longa tempo.",
|
||||
"TaskKeyframeExtractor": "Eltiri Ĉefkadrojn",
|
||||
"External": "Ekstera"
|
||||
}
|
||||
|
||||
@@ -120,5 +120,7 @@
|
||||
"Forced": "Forzado",
|
||||
"Default": "Predeterminado",
|
||||
"TaskOptimizeDatabase": "Optimizar base de datos",
|
||||
"TaskOptimizeDatabaseDescription": "Compacta la base de datos y trunca el espacio libre. Puede mejorar el rendimiento si se realiza esta tarea después de escanear la biblioteca o después de realizar otros cambios que impliquen modificar la base de datos."
|
||||
"TaskOptimizeDatabaseDescription": "Compacta la base de datos y trunca el espacio libre. Puede mejorar el rendimiento si se realiza esta tarea después de escanear la biblioteca o después de realizar otros cambios que impliquen modificar la base de datos.",
|
||||
"TaskKeyframeExtractorDescription": "Extrae los cuadros clave de los archivos de vídeo para crear listas HLS más precisas. Esta tarea puede tardar un buen rato.",
|
||||
"TaskKeyframeExtractor": "Extractor de Cuadros Clave"
|
||||
}
|
||||
|
||||
@@ -120,5 +120,7 @@
|
||||
"Forced": "Forzado",
|
||||
"Default": "Predeterminado",
|
||||
"TaskOptimizeDatabase": "Optimizar la base de datos",
|
||||
"TaskOptimizeDatabaseDescription": "Compacta y libera el espacio libre en la base de datos. Ejecutar esta tarea tras escanear la biblioteca o hacer cambios que impliquen modificaciones en la base de datos puede mejorar el rendimiento."
|
||||
"TaskOptimizeDatabaseDescription": "Compacta y libera el espacio libre en la base de datos. Ejecutar esta tarea tras escanear la biblioteca o hacer cambios que impliquen modificaciones en la base de datos puede mejorar el rendimiento.",
|
||||
"TaskKeyframeExtractorDescription": "Extrae los fotogramas clave de los archivos de vídeo para crear listas HLS más precisas. Esta tarea puede tardar mucho tiempo.",
|
||||
"TaskKeyframeExtractor": "Extractor de Fotogramas Clave"
|
||||
}
|
||||
|
||||
120
Emby.Server.Implementations/Localization/Core/eu.json
Normal file
120
Emby.Server.Implementations/Localization/Core/eu.json
Normal file
@@ -0,0 +1,120 @@
|
||||
{
|
||||
"ValueSpecialEpisodeName": "Berezia - {0}",
|
||||
"Sync": "Sinkronizatu",
|
||||
"Songs": "Abestiak",
|
||||
"Shows": "Serieak",
|
||||
"Playlists": "Erreprodukzio-zerrendak",
|
||||
"Photos": "Argazkiak",
|
||||
"MusicVideos": "Bideo musikalak",
|
||||
"Movies": "Filmak",
|
||||
"HeaderContinueWatching": "Ikusten jarraitu",
|
||||
"HeaderAlbumArtists": "Albumeko artistak",
|
||||
"Genres": "Generoak",
|
||||
"Folders": "Karpetak",
|
||||
"Favorites": "Gogokoak",
|
||||
"Default": "Lehenetsia",
|
||||
"Collections": "Bildumak",
|
||||
"Channels": "Kanalak",
|
||||
"Books": "Liburuak",
|
||||
"Artists": "Artistak",
|
||||
"Albums": "Albumak",
|
||||
"TaskOptimizeDatabase": "Datu basea optimizatu",
|
||||
"TaskDownloadMissingSubtitlesDescription": "Metadataren konfigurazioan oinarrituta falta diren azpitituluak bilatzen ditu interneten.",
|
||||
"TaskDownloadMissingSubtitles": "Falta diren azpitituluak deskargatu",
|
||||
"TaskRefreshChannelsDescription": "Internet kanalen informazioa eguneratu.",
|
||||
"TaskRefreshChannels": "Kanalak eguneratu",
|
||||
"TaskCleanTranscodeDescription": "Egun bat baino zaharragoak diren transcode fitxategiak ezabatzen ditu.",
|
||||
"TaskCleanTranscode": "Transcode direktorioa garbitu",
|
||||
"TaskUpdatePluginsDescription": "Automatikoki eguneratzeko konfiguratutako pluginen eguneraketak deskargatu eta instalatzen ditu.",
|
||||
"TaskUpdatePlugins": "Pluginak eguneratu",
|
||||
"TaskRefreshPeopleDescription": "Zure liburutegiko aktore eta zuzendarien metadata eguneratzen du.",
|
||||
"TaskRefreshPeople": "Jendea eguneratu",
|
||||
"TaskCleanLogsDescription": "{0} egun baino zaharragoak diren log fitxategiak ezabatzen ditu.",
|
||||
"TaskCleanLogs": "Log direktorioa garbitu",
|
||||
"TaskRefreshLibraryDescription": "Zure multimedia liburutegia eskaneatzen du fitxategi berriak eta metadatak eguneratzeko.",
|
||||
"TaskRefreshLibrary": "Multimedia Liburutegia eskaneatu",
|
||||
"TaskRefreshChapterImagesDescription": "Kapituluak dituzten bideoen miniaturak sortzen ditu.",
|
||||
"TaskRefreshChapterImages": "Kapituluen irudiak erauzi",
|
||||
"TaskCleanCacheDescription": "Sistemak behar ez dituen cache fitxategiak ezabatzen ditu.",
|
||||
"TaskCleanCache": "Cache Directorioa garbitu",
|
||||
"TaskCleanActivityLogDescription": "Konfiguratuta data baino zaharragoak diren log-ak ezabatu.",
|
||||
"TaskCleanActivityLog": "Erabilera Log-a garbitu",
|
||||
"TasksChannelsCategory": "Internet Kanalak",
|
||||
"TasksApplicationCategory": "Aplikazioa",
|
||||
"TasksLibraryCategory": "Liburutegia",
|
||||
"TasksMaintenanceCategory": "Mantenua",
|
||||
"VersionNumber": "Bertsioa {0}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} zure multimedia liburutegian gehitu da",
|
||||
"UserStoppedPlayingItemWithValues": "{0}-ek {1} ikusteaz bukatu du {2}-(a)n",
|
||||
"UserStartedPlayingItemWithValues": "{0} {1} ikusten ari da {2}-(a)n",
|
||||
"UserPolicyUpdatedWithName": "{0} Erabiltzailearen politikak aldatu dira",
|
||||
"UserPasswordChangedWithName": "{0} Erabiltzailearen pasahitza aldatu da",
|
||||
"UserOnlineFromDevice": "{0} online dago {1}-tik",
|
||||
"UserOfflineFromDevice": "{0} {1}-tik deskonektatu da",
|
||||
"UserLockedOutWithName": "{0} Erabiltzailea blokeatu da",
|
||||
"UserDownloadingItemWithValues": "{1} {0}-tik deskargatzen",
|
||||
"UserDeletedWithName": "{0} Erabiltzailea ezabatu da",
|
||||
"UserCreatedWithName": "{0} Erabiltzailea sortu da",
|
||||
"User": "Erabiltzailea",
|
||||
"Undefined": "Ezezaguna",
|
||||
"TvShows": "TB showak",
|
||||
"System": "Sistema",
|
||||
"SubtitleDownloadFailureFromForItem": "{1}-en azpitutuluak {0} deskargatzean huts egin du",
|
||||
"StartupEmbyServerIsLoading": "Jellyfin zerbitzaria kargatzen. Saiatu berriro beranduxeago.",
|
||||
"ServerNameNeedsToBeRestarted": "{0} berrabiarazi behar da",
|
||||
"ScheduledTaskStartedWithName": "{0} hasi da",
|
||||
"ScheduledTaskFailedWithName": "{0} huts egin du",
|
||||
"PluginUpdatedWithName": "{0} eguneratu da",
|
||||
"PluginUninstalledWithName": "{0} desinstalatu da",
|
||||
"PluginInstalledWithName": "{0} instalatu da",
|
||||
"Plugin": "Plugin",
|
||||
"NotificationOptionVideoPlaybackStopped": "Bideoa geldituta",
|
||||
"NotificationOptionVideoPlayback": "Bideoa martxan",
|
||||
"NotificationOptionUserLockedOut": "Erabiltzailea blokeatua",
|
||||
"NotificationOptionTaskFailed": "Programatutako atazak huts egin du",
|
||||
"NotificationOptionServerRestartRequired": "Zerbitzaria berrabiarazi behar da",
|
||||
"NotificationOptionPluginUpdateInstalled": "Pluginaren eguneraketa instalatua",
|
||||
"NotificationOptionPluginUninstalled": "Plugina desinstalatua",
|
||||
"NotificationOptionPluginInstalled": "Plugina instalatua",
|
||||
"NotificationOptionPluginError": "Pluginak huts egin du",
|
||||
"NotificationOptionNewLibraryContent": "Eduki berria gehitua",
|
||||
"NotificationOptionInstallationFailed": "Instalazioak huts egin du",
|
||||
"NotificationOptionCameraImageUploaded": "Kamerako irudia igota",
|
||||
"NotificationOptionAudioPlaybackStopped": "Audioa gelditua",
|
||||
"NotificationOptionAudioPlayback": "Audioa martxan",
|
||||
"NotificationOptionApplicationUpdateInstalled": "Aplikazioaren eguneraketa instalatua",
|
||||
"NotificationOptionApplicationUpdateAvailable": "Aplikazioaren eguneraketa eskuragarri",
|
||||
"NewVersionIsAvailable": "Jellyfin zerbitzariaren bertsio berria deskargatzeko eskuragarri dago.",
|
||||
"NameSeasonUnknown": "Denboraldi ezezaguna",
|
||||
"NameSeasonNumber": "{0} Denboraldia",
|
||||
"NameInstallFailed": "{0} instalazioak huts egin du",
|
||||
"Music": "Musika",
|
||||
"MixedContent": "Denetariko edukia",
|
||||
"MessageServerConfigurationUpdated": "Zerbitzariaren konfigurazioa eguneratu da",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "Zerbitzariaren konfigurazio {0} atala eguneratu da",
|
||||
"MessageApplicationUpdatedTo": "Jellyfin zerbitzaria {0}-ra eguneratu da",
|
||||
"MessageApplicationUpdated": "Jellyfin zerbitzaria eguneratu da",
|
||||
"Latest": "Azkena",
|
||||
"LabelRunningTimeValue": "Denbora martxan: {0}",
|
||||
"LabelIpAddressValue": "IP helbidea: {0}",
|
||||
"ItemRemovedWithName": "{0} liburutegitik ezabatu da",
|
||||
"ItemAddedWithName": "{0} liburutegira gehitu da",
|
||||
"HomeVideos": "Etxeko bideoak",
|
||||
"HeaderNextUp": "Hurrengoa",
|
||||
"HeaderLiveTV": "Zuzeneko TB",
|
||||
"HeaderFavoriteSongs": "Gogoko abestiak",
|
||||
"HeaderFavoriteShows": "Gogoko showak",
|
||||
"HeaderFavoriteEpisodes": "Gogoko atalak",
|
||||
"HeaderFavoriteArtists": "Gogoko artistak",
|
||||
"HeaderFavoriteAlbums": "Gogoko albumak",
|
||||
"Forced": "Behartuta",
|
||||
"FailedLoginAttemptWithUserName": "Login egiten akatsa, saiatu hemen {0}",
|
||||
"External": "Kanpokoa",
|
||||
"DeviceOnlineWithName": "{0} konektatu da",
|
||||
"DeviceOfflineWithName": "{0} deskonektatu da",
|
||||
"ChapterNameValue": "{0} Kapitulua",
|
||||
"CameraImageUploadedFrom": "{0}-tik kamera irudi berri bat igo da",
|
||||
"AuthenticationSucceededWithUserName": "{0} ongi autentifikatu da",
|
||||
"Application": "Aplikazioa",
|
||||
"AppDeviceValues": "App: {0}, Gailua: {1}"
|
||||
}
|
||||
@@ -119,5 +119,9 @@
|
||||
"TaskCleanActivityLogDescription": "ورودیهای قدیمیتر از سن تنظیم شده در سیاهه فعالیت را حذف میکند.",
|
||||
"TaskCleanActivityLog": "پاکسازی سیاهه فعالیت",
|
||||
"Undefined": "تعریف نشده",
|
||||
"TaskOptimizeDatabase": "بهینه سازی پایگاه داده"
|
||||
"TaskOptimizeDatabase": "بهینه سازی پایگاه داده",
|
||||
"TaskOptimizeDatabaseDescription": "فشرده سازی پایگاه داده و باز کردن فضای آزاد.اجرای این گزینه بعد از اسکن کردن کتابخانه یا تغییرات دیگر که روی پایگاه داده تأثیر میگذارند میتواند کارایی را بهبود ببخشد.",
|
||||
"TaskKeyframeExtractorDescription": "فریم های کلیدی را از فایل های ویدئویی استخراج می کند تا لیست های پخش HLS دقیق تری ایجاد کند. این کار ممکن است برای مدت طولانی اجرا شود.",
|
||||
"TaskKeyframeExtractor": "استخراج کننده فریم کلیدی",
|
||||
"External": "خارجی"
|
||||
}
|
||||
|
||||
@@ -119,5 +119,8 @@
|
||||
"TaskCleanActivityLog": "Tyhjennä toimintahistoria",
|
||||
"Undefined": "Määrittelemätön",
|
||||
"TaskOptimizeDatabaseDescription": "Tiivistää ja puhdistaa tietokannan. Tämän toiminnon suorittaminen kirjastojen skannauksen tai muiden tietokantaan liittyvien muutoksien jälkeen voi parantaa suorituskykyä.",
|
||||
"TaskOptimizeDatabase": "Optimoi tietokanta"
|
||||
"TaskOptimizeDatabase": "Optimoi tietokanta",
|
||||
"TaskKeyframeExtractorDescription": "Purkaa videotiedostojen avainkuvat tarkempien HLS-toistolistojen luomiseksi. Tehtävä saattaa kestää huomattavan pitkään.",
|
||||
"TaskKeyframeExtractor": "Avainkuvien purkain",
|
||||
"External": "Ulkoinen"
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"Albums": "Albums",
|
||||
"AppDeviceValues": "Application : {0}, Appareil : {1}",
|
||||
"Application": "Applications",
|
||||
"Application": "Application",
|
||||
"Artists": "Artistes",
|
||||
"AuthenticationSucceededWithUserName": "{0} authentifié avec succès",
|
||||
"Books": "Livres",
|
||||
@@ -120,5 +120,7 @@
|
||||
"Forced": "Forcé",
|
||||
"Default": "Par défaut",
|
||||
"TaskOptimizeDatabaseDescription": "Réduit les espaces vides/inutiles et compacte la base de données. Utiliser cette fonction après une mise à jour de la bibliothèque ou toute autre modification de la base de données peut améliorer les performances du serveur.",
|
||||
"TaskOptimizeDatabase": "Optimiser la base de données"
|
||||
"TaskOptimizeDatabase": "Optimiser la base de données",
|
||||
"TaskKeyframeExtractorDescription": "Extrait les images clés des fichiers vidéo pour créer des listes de lecture HLS plus précises. Cette tâche peut durer très longtemps.",
|
||||
"TaskKeyframeExtractor": "Extracteur d'image clé"
|
||||
}
|
||||
|
||||
@@ -119,5 +119,6 @@
|
||||
"Undefined": "לא מוגדר",
|
||||
"Forced": "כפוי",
|
||||
"Default": "ברירת מחדל",
|
||||
"TaskOptimizeDatabase": "מיטוב מסד נתונים"
|
||||
"TaskOptimizeDatabase": "מיטוב מסד נתונים",
|
||||
"TaskOptimizeDatabaseDescription": "דוחס את מסד הנתונים ומוריד את שטח האחסון שבשימוש. הרצה של פעולה זו לאחר סריקת הספרייה או שינויים אחרים שמשפיעים על מסד הנתונים יכולה לשפר ביצועים."
|
||||
}
|
||||
|
||||
@@ -61,5 +61,10 @@
|
||||
"LabelRunningTimeValue": "चलने का समय: {0}",
|
||||
"ItemAddedWithName": "{0} को लाइब्रेरी में जोड़ा गया",
|
||||
"Inherit": "इनहेरिट",
|
||||
"NotificationOptionVideoPlaybackStopped": "चलचित्र रुका हुआ"
|
||||
"NotificationOptionVideoPlaybackStopped": "चलचित्र रुका हुआ",
|
||||
"PluginUninstalledWithName": "{0} अनइंस्टॉल हुए",
|
||||
"PluginInstalledWithName": "{0} इंस्टॉल हुए",
|
||||
"Plugin": "प्लग-इन",
|
||||
"Playlists": "प्लेलिस्ट",
|
||||
"Photos": "तस्वीरें"
|
||||
}
|
||||
|
||||
@@ -120,5 +120,8 @@
|
||||
"Forced": "Kényszerített",
|
||||
"Default": "Alapértelmezett",
|
||||
"TaskOptimizeDatabaseDescription": "Tömöríti az adatbázist és csonkolja a szabad helyet. A feladat futtatása a könyvtár beolvasása után, vagy egyéb, adatbázis-módosítást igénylő változtatások végrehajtása javíthatja a teljesítményt.",
|
||||
"TaskOptimizeDatabase": "Adatbázis optimalizálása"
|
||||
"TaskOptimizeDatabase": "Adatbázis optimalizálása",
|
||||
"TaskKeyframeExtractor": "Kulcskockák kibontása",
|
||||
"TaskKeyframeExtractorDescription": "Kulcskockákat bont ki a videofájlokból, hogy pontosabb HLS lejátszási listákat hozzon létre. Ez a feladat hosszú ideig tarthat.",
|
||||
"External": "Külső"
|
||||
}
|
||||
|
||||
@@ -120,5 +120,7 @@
|
||||
"Forced": "Forzato",
|
||||
"Default": "Predefinito",
|
||||
"TaskOptimizeDatabaseDescription": "Compatta Database e tronca spazi liberi. Eseguire questa azione dopo la scansione o dopo aver fatto altri cambiamenti inerenti il database potrebbe aumentarne la performance.",
|
||||
"TaskOptimizeDatabase": "Ottimizza Database"
|
||||
"TaskOptimizeDatabase": "Ottimizza Database",
|
||||
"TaskKeyframeExtractor": "Estrattore di Keyframe",
|
||||
"TaskKeyframeExtractorDescription": "Estrae i keyframe dai video per creare migliori playlist HLS. Questa procedura potrebbe richiedere molto tempo."
|
||||
}
|
||||
|
||||
@@ -119,5 +119,8 @@
|
||||
"Forced": "強制",
|
||||
"Default": "デフォルト",
|
||||
"TaskOptimizeDatabaseDescription": "データベースをコンパクトにして、空き領域を切り詰めます。メディアライブラリのスキャン後でこのタスクを実行するとパフォーマンスが向上する可能性があります。",
|
||||
"TaskOptimizeDatabase": "データベースの最適化"
|
||||
"TaskOptimizeDatabase": "データベースの最適化",
|
||||
"TaskKeyframeExtractorDescription": "より正確なHLSプレイリストを作成するため、動画ファイルからキーフレームを抽出する。この処理には時間がかかる場合があります。",
|
||||
"TaskKeyframeExtractor": "キーフレーム抽出",
|
||||
"External": "外部"
|
||||
}
|
||||
|
||||
@@ -120,5 +120,8 @@
|
||||
"TaskCleanCacheDescription": "Jüiede qajet emes keştelgen faildardy joiady.",
|
||||
"TaskCleanActivityLogDescription": "Äreket jūrnalyndağy teñşelgen jasynan asqan jazbalary joiady.",
|
||||
"TaskOptimizeDatabaseDescription": "Derekqordy qysyp, bos oryndy qysqartady. Būl tapsyrmany tasyğyşhanany skanerlegennen keiın nemese derekqorğa meñzeitın basqa özgertuler ıstelgennen keiın oryndau önımdılıktı damytuy mümkın.",
|
||||
"TaskOptimizeDatabase": "Derekqordy oñtailandyru"
|
||||
"TaskOptimizeDatabase": "Derekqordy oñtailandyru",
|
||||
"TaskKeyframeExtractorDescription": "Naqtyraq HLS oynatu tızımderın jasau üşın beinefaildardan negızgı kadrlardy şyğarady. Būl tapsyrma ūzaq uaqytqa sozyluy mümkın.",
|
||||
"TaskKeyframeExtractor": "Negızgı kadrlardy şyğaru",
|
||||
"External": "Syrtqy"
|
||||
}
|
||||
|
||||
7
Emby.Server.Implementations/Localization/Core/kn.json
Normal file
7
Emby.Server.Implementations/Localization/Core/kn.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"TaskDownloadMissingSubtitlesDescription": "ಮೆಟಾಡೇಟಾ ಕಾನ್ಫಿಗರೇಶನ್ ಆಧಾರದ ಮೇಲೆ ಕಾಣೆಯಾದ ಉಪಶೀರ್ಷಿಕೆಗಳಿಗಾಗಿ ಅಂತರ್ಜಾಲದಲ್ಲಿ ಹುಡುಕುತ್ತದೆ.",
|
||||
"TaskOptimizeDatabase": "ಡೇಟಾಬೇಸ್ ಅನ್ನು ಆಪ್ಟಿಮೈಜ್ ಮಾಡಿ",
|
||||
"TaskOptimizeDatabaseDescription": "ಡೇಟಾಬೇಸ್ ಅನ್ನು ಕಾಂಪ್ಯಾಕ್ಟ್ ಮಾಡುತ್ತದೆ ಮತ್ತು ಮುಕ್ತ ಜಾಗವನ್ನು ಮೊಟಕುಗೊಳಿಸುತ್ತದೆ. ಲೈಬ್ರರಿಯನ್ನು ಸ್ಕ್ಯಾನ್ ಮಾಡಿದ ನಂತರ ಈ ಕಾರ್ಯವನ್ನು ನಡೆಸುವುದು ಅಥವಾ ಡೇಟಾಬೇಸ್ ಮಾರ್ಪಾಡುಗಳನ್ನು ಸೂಚಿಸುವ ಇತರ ಬದಲಾವಣೆಗಳನ್ನು ಮಾಡುವುದರಿಂದ ಕಾರ್ಯಕ್ಷಮತೆಯನ್ನು ಸುಧಾರಿಸಬಹುದು.",
|
||||
"TaskKeyframeExtractor": "ಕೀಫ್ರೇಮ್ ಎಕ್ಸ್ಟ್ರಾಕ್ಟರ್",
|
||||
"TaskKeyframeExtractorDescription": "ಹೆಚ್ಚು ನಿಖರವಾದ HLS ಪ್ಲೇಪಟ್ಟಿಗಳನ್ನು ರಚಿಸಲು ವೀಡಿಯೊ ಫೈಲ್ಗಳಿಂದ ಕೀಫ್ರೇಮ್ಗಳನ್ನು ಹೊರತೆಗೆಯುತ್ತದೆ. ಈ ಕಾರ್ಯವು ದೀರ್ಘಕಾಲದವರೆಗೆ ನಡೆಯಬಹುದು."
|
||||
}
|
||||
@@ -58,5 +58,47 @@
|
||||
"Application": "अॅप्लिकेशन",
|
||||
"AppDeviceValues": "अॅप: {0}, यंत्र: {1}",
|
||||
"Collections": "संग्रह",
|
||||
"ChapterNameValue": "धडा {0}"
|
||||
"ChapterNameValue": "धडा {0}",
|
||||
"TaskDownloadMissingSubtitlesDescription": "नसलेल्या उपशिर्षकांचा मेटाडॅटा कॉन्फिग्युरेशनप्रमाणे इन्टरनेटवर शोध घेतो.",
|
||||
"TaskRefreshChannelsDescription": "इन्टरनेट वाहिन्यांची माहिती ताजी करतो.",
|
||||
"TaskUpdatePluginsDescription": "आपोआप अपडेट करण्यासाठी कॉन्फिगर केलेल्या प्लगइनसाठी अपडेट डाउनलोड करून इन्स्टॉल करतो.",
|
||||
"TaskRefreshChannels": "वाहिन्या ताज्या करा",
|
||||
"TaskRefreshPeopleDescription": "आपल्या माध्यम संग्रहातील अभिनेत्यांचा व दिग्दर्शकांचा मेटाडॅटा ताजा करतो.",
|
||||
"TaskRefreshPeople": "लोकांची माहिती ताजी करा",
|
||||
"TaskRefreshLibraryDescription": "माध्यम संग्रह स्कॅन करून नवीन फायली शोधतो व मेटाडॅटा ताजे करतो.",
|
||||
"TaskRefreshLibrary": "माध्यम संग्रह स्कॅन करा",
|
||||
"TaskRefreshChapterImagesDescription": "अध्याय असलेल्या व्हिडियोंसाठी थंबनेल चित्र बनवतो.",
|
||||
"TaskRefreshChapterImages": "अध्याय चित्र काढून घ्या",
|
||||
"TasksMaintenanceCategory": "देखरेख",
|
||||
"ValueHasBeenAddedToLibrary": "{0} हे तुमच्या माध्यम संग्रहात जोडण्यात आले आहे",
|
||||
"UserStoppedPlayingItemWithValues": "{0} यांचं {2} वर {1} पूर्णपणे प्ले करून झालं आहे",
|
||||
"UserStartedPlayingItemWithValues": "{0} हे {2} वर {1} प्ले करत आहे",
|
||||
"UserDownloadingItemWithValues": "{0} हे {1} डाउनलोड करत आहे",
|
||||
"System": "प्रणाली",
|
||||
"Undefined": "अव्याख्यात",
|
||||
"Sync": "सिंक",
|
||||
"ServerNameNeedsToBeRestarted": "{0} याला बंद करून पुन्हा सुरू करायची गरज आहे",
|
||||
"SubtitleDownloadFailureFromForItem": "{0} येथून {1} यासाठी उपशिर्षक डाउनलोड करण्यात अपयश",
|
||||
"ScheduledTaskStartedWithName": "{0} सुरू झाले",
|
||||
"ScheduledTaskFailedWithName": "{0} अपयशी झाले",
|
||||
"ProviderValue": "पुरवणारा: {0}",
|
||||
"PluginUpdatedWithName": "{0} अपडेट केले",
|
||||
"PluginUninstalledWithName": "{0} अनिन्स्टॉल केले",
|
||||
"PluginInstalledWithName": "{0} इन्स्टॉल केले",
|
||||
"NotificationOptionVideoPlaybackStopped": "व्हिडियो प्लेबॅक बंद केले",
|
||||
"NotificationOptionVideoPlayback": "व्हिडियो प्लेबॅक सुरू केले",
|
||||
"NotificationOptionTaskFailed": "अनुसूचित कार्यात अपयश",
|
||||
"NotificationOptionServerRestartRequired": "सर्व्हर बंद करून पुन्हा सुरू करावा लागेल",
|
||||
"NotificationOptionPluginUpdateInstalled": "प्लगइन अपडेट इन्स्टॉल झाले",
|
||||
"NotificationOptionPluginUninstalled": "प्लगइन अनिन्स्टॉल झाले",
|
||||
"NotificationOptionPluginInstalled": "प्लगइन इन्स्टॉल झाले",
|
||||
"NotificationOptionPluginError": "प्लगइनमध्ये अपयश",
|
||||
"NotificationOptionNewLibraryContent": "नवीन सामग्री जोडली गेली",
|
||||
"NotificationOptionInstallationFailed": "इन्स्टॉल करण्यात अपयश",
|
||||
"NotificationOptionAudioPlayback": "ऑडियो प्लेबॅक सुरू झाले",
|
||||
"NotificationOptionAudioPlaybackStopped": "ऑडियो प्लेबॅक बंद झाले",
|
||||
"MixedContent": "मिश्रित सामग्री",
|
||||
"LabelRunningTimeValue": "चालू काल: {0}",
|
||||
"HeaderContinueWatching": "बघणे चालू ठेवा",
|
||||
"Default": "डीफॉल्ट"
|
||||
}
|
||||
|
||||
@@ -4,5 +4,120 @@
|
||||
"Channels": "ချန်နယ်များ",
|
||||
"Books": "စာအုပ်များ",
|
||||
"Artists": "အနုပညာရှင်များ",
|
||||
"Albums": "အခွေများ"
|
||||
"Albums": "အခွေများ",
|
||||
"TaskOptimizeDatabaseDescription": "ဒေတာဘေ့စ်ကို ကျစ်လစ်စေပြီး နေရာလွတ်များကို ဖြတ်တောက်ပေးသည်။ စာကြည့်တိုက်ကို စကင်န်ဖတ်ပြီးနောက် ဤလုပ်ငန်းကို လုပ်ဆောင်ခြင်း သို့မဟုတ် ဒေတာဘေ့စ်မွမ်းမံမှုများ စွမ်းဆောင်ရည်ကို မြှင့်တင်ပေးနိုင်သည်ဟု ရည်ညွှန်းသော အခြားပြောင်းလဲမှုများကို လုပ်ဆောင်ခြင်း။.",
|
||||
"TaskOptimizeDatabase": "ဒေတာဘေ့စ်ကို အကောင်းဆုံးဖြစ်အောင်လုပ်ပါ။",
|
||||
"TaskDownloadMissingSubtitlesDescription": "မက်တာဒေတာ ဖွဲ့စည်းမှုပုံစံအပေါ် အခြေခံ၍ ပျောက်ဆုံးနေသော စာတန်းထိုးများအတွက် အင်တာနက်ကို ရှာဖွေသည်။",
|
||||
"TaskDownloadMissingSubtitles": "ပျောက်ဆုံးနေသော စာတန်းထိုးများကို ဒေါင်းလုဒ်လုပ်ပါ။",
|
||||
"TaskRefreshChannelsDescription": "အင်တာနက်ချန်နယ်အချက်အလက်ကို ပြန်လည်စတင်သည်။",
|
||||
"TaskRefreshChannels": "ချန်နယ်များကို ပြန်လည်စတင်ပါ။",
|
||||
"TaskCleanTranscodeDescription": "သက်တမ်း တစ်ရက်ထက်ပိုသော အသွင်ပြောင်းကုဒ်ဖိုင်များကို ဖျက်ပါ။",
|
||||
"TaskCleanTranscode": "Transcode လမ်းညွှန်ကို သန့်ရှင်းပါ။",
|
||||
"TaskUpdatePluginsDescription": "အလိုအလျောက် အပ်ဒိတ်လုပ်ရန် စီစဉ်ထားသော ပလပ်အင်များအတွက် အပ်ဒိတ်များကို ဒေါင်းလုဒ်လုပ်ပြီး ထည့်သွင်းပါ။",
|
||||
"TaskUpdatePlugins": "ပလပ်အင်များကို အပ်ဒိတ်လုပ်ပါ။",
|
||||
"TaskRefreshPeopleDescription": "သင့်မီဒီယာစာကြည့်တိုက်ရှိ သရုပ်ဆောင်များနှင့် ဒါရိုက်တာများအတွက် မက်တာဒေတာကို အပ်ဒိတ်လုပ်ပါ။",
|
||||
"TaskRefreshPeople": "လူများကို ပြန်လည်ဆန်းသစ်ပါ။",
|
||||
"TaskCleanLogsDescription": "{0} ရက်ထက်ပိုသော မှတ်တမ်းဖိုင်များကို ဖျက်သည်။",
|
||||
"TaskCleanLogs": "မှတ်တမ်းလမ်းညွှန်ကို သန့်ရှင်းပါ။",
|
||||
"TaskRefreshLibraryDescription": "ဖိုင်အသစ်များအတွက် သင့်မီဒီယာဒစ်ဂျစ်တိုက်ကို စကင်န်ဖတ်ပြီး မက်တာဒေတာကို ပြန်လည်စတင်ပါ။",
|
||||
"TaskRefreshLibrary": "မီဒီယာစာကြည့်တိုက်ကို စကင်န်ဖတ်ပါ။",
|
||||
"TaskRefreshChapterImagesDescription": "အခန်းများပါရှိသော ဗီဒီယိုများအတွက် ပုံသေးများကို ဖန်တီးပါ။",
|
||||
"TaskRefreshChapterImages": "အခန်းပုံများကို ထုတ်ယူပါ။",
|
||||
"TaskCleanCacheDescription": "စနစ်မှ မလိုအပ်တော့သော ကက်ရှ်ဖိုင်များကို ဖျက်ပါ။.",
|
||||
"TaskCleanCache": "Cache Directory ကို ရှင်းပါ။",
|
||||
"TaskCleanActivityLogDescription": "စီစဉ်သတ်မှတ်ထားသော အသက်ထက် ပိုကြီးသော လုပ်ဆောင်ချက်မှတ်တမ်းများကို ဖျက်ပါ။",
|
||||
"TaskCleanActivityLog": "လုပ်ဆောင်ချက်မှတ်တမ်းကို ရှင်းလင်းပါ။",
|
||||
"TasksChannelsCategory": "အင်တာနက်ချန်နယ်များ",
|
||||
"TasksApplicationCategory": "အပလီကေးရှင်း",
|
||||
"TasksLibraryCategory": "စာကြည့်တိုက်",
|
||||
"TasksMaintenanceCategory": "ထိန်းသိမ်းခြင်း",
|
||||
"VersionNumber": "ဗားရှင်း {0}",
|
||||
"ValueSpecialEpisodeName": "အထူး- {0}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} ကို သင့်မီဒီယာဒစ်ဂျစ်တိုက်သို့ ပေါင်းထည့်လိုက်ပါပြီ။",
|
||||
"UserStoppedPlayingItemWithValues": "{0} သည် {1} ကို {2} တွင် ဖွင့်ပြီးပါပြီ",
|
||||
"UserStartedPlayingItemWithValues": "{0} သည် {1} ကို {2} တွင် ပြသနေသည်",
|
||||
"UserPolicyUpdatedWithName": "{0} အတွက် အသုံးပြုသူမူဝါဒကို အပ်ဒိတ်လုပ်ပြီးပါပြီ",
|
||||
"UserPasswordChangedWithName": "အသုံးပြုသူ {0} အတွက် စကားဝှက်ကို ပြောင်းထားသည်",
|
||||
"UserOnlineFromDevice": "{0} သည် {1} မှ အွန်လိုင်းဖြစ်သည်",
|
||||
"UserOfflineFromDevice": "{0} သည် {1} မှ ချိတ်ဆက်မှုပြတ်တောက်သွားသည်",
|
||||
"UserLockedOutWithName": "အသုံးပြုသူ {0} အား လော့ခ်ချထားသည်။",
|
||||
"UserDownloadingItemWithValues": "{0} သည် {1} ကို ဒေါင်းလုဒ်လုပ်နေသည်",
|
||||
"UserDeletedWithName": "အသုံးပြုသူ {0} ကို ဖျက်လိုက်ပါပြီ။",
|
||||
"UserCreatedWithName": "အသုံးပြုသူ {0} ကို ဖန်တီးပြီးပါပြီ။",
|
||||
"User": "အသုံးပြုသူ",
|
||||
"Undefined": "သတ်မှတ်မထားသော",
|
||||
"TvShows": "တီဗီရှိုးများ",
|
||||
"System": "စနစ်",
|
||||
"Sync": "ထပ်တူကျသည်။",
|
||||
"SubtitleDownloadFailureFromForItem": "စာတန်းထိုးများကို {1} အတွက် {0} မှ ဒေါင်းလုဒ်လုပ်၍ မရပါ",
|
||||
"StartupEmbyServerIsLoading": "Jellyfin ဆာဗာကို ဖွင့်နေပါသည်။ ခဏနေ ထပ်စမ်းကြည့်ပါ။",
|
||||
"Songs": "သီချင်းများ",
|
||||
"Shows": "ရှိုးပွဲ",
|
||||
"ServerNameNeedsToBeRestarted": "{0} ကို ပြန်လည်စတင်ရန် လိုအပ်သည်။",
|
||||
"ScheduledTaskStartedWithName": "{0} စတင်ခဲ့သည်။",
|
||||
"ScheduledTaskFailedWithName": "{0} မအောင်မြင်ပါ။",
|
||||
"ProviderValue": "ဝန်ဆောင်မှုပေးသူ- {0}",
|
||||
"PluginUpdatedWithName": "{0} ကို အပ်ဒိတ်လုပ်ထားသည်။",
|
||||
"PluginUninstalledWithName": "{0} ကို ဖြုတ်လိုက်ပါပြီ။",
|
||||
"PluginInstalledWithName": "{0} ကို ထည့်သွင်းခဲ့သည်။",
|
||||
"Plugin": "ပလပ်အင်",
|
||||
"Playlists": "အစီအစဉ်များ",
|
||||
"Photos": "ဓာတ်ပုံများ",
|
||||
"NotificationOptionVideoPlaybackStopped": "ဗီဒီယိုပြန်ဖွင့်ခြင်းကို ရပ်သွားသည်။",
|
||||
"NotificationOptionVideoPlayback": "ဗီဒီယိုဖွင့်ခြင်း စတင်ပါပြီ။",
|
||||
"NotificationOptionUserLockedOut": "အသုံးပြုသူ ထွက်သွားသည်။",
|
||||
"NotificationOptionTaskFailed": "စီစဉ်ထားသော အလုပ်ပျက်ကွက်",
|
||||
"NotificationOptionServerRestartRequired": "ဆာဗာ ပြန်လည်စတင်ရန် လိုအပ်သည်။",
|
||||
"NotificationOptionPluginUpdateInstalled": "ပလပ်အင် အပ်ဒိတ် ထည့်သွင်းပြီးပါပြီ။",
|
||||
"NotificationOptionPluginUninstalled": "ပလပ်အင်ကို ဖြုတ်လိုက်ပါပြီ။",
|
||||
"NotificationOptionPluginInstalled": "ပလပ်အင် ထည့်သွင်းထားသည်။",
|
||||
"NotificationOptionPluginError": "ပလပ်အင် ချို့ယွင်းခြင်း။",
|
||||
"NotificationOptionNewLibraryContent": "အကြောင်းအရာအသစ် ထပ်ထည့်ထားပါတယ်။",
|
||||
"NotificationOptionInstallationFailed": "တပ်ဆင်မှု မအောင်မြင်ပါ။",
|
||||
"NotificationOptionCameraImageUploaded": "ကင်မရာပုံ အပ်လုဒ်လုပ်ထားသည်။",
|
||||
"NotificationOptionAudioPlaybackStopped": "အသံပြန်ဖွင့်ခြင်းကို ရပ်သွားသည်။",
|
||||
"NotificationOptionAudioPlayback": "အသံပြန်ဖွင့်ခြင်း စတင်ပါပြီ။",
|
||||
"NotificationOptionApplicationUpdateInstalled": "အပလီကေးရှင်း အပ်ဒိတ်ကို ထည့်သွင်းထားသည်။",
|
||||
"NotificationOptionApplicationUpdateAvailable": "အပလီကေးရှင်း အပ်ဒိတ် ရနိုင်ပါပြီ။",
|
||||
"NewVersionIsAvailable": "Jellyfin Server ၏ ဗားရှင်းအသစ်ကို ဒေါင်းလုဒ်လုပ်နိုင်ပါသည်။",
|
||||
"NameSeasonUnknown": "အမည််မသိ ဇာတ်လမ်းတွဲ",
|
||||
"NameSeasonNumber": "ဇာတ်လမ်းတွဲ {0}",
|
||||
"NameInstallFailed": "{0} ထည့်သွင်းမှု မအောင်မြင်ပါ။",
|
||||
"MusicVideos": "ဂီတဗီဒီယိုများ",
|
||||
"Music": "တေးဂီတ",
|
||||
"Movies": "ရုပ်ရှင်များ",
|
||||
"MixedContent": "ရောနှောပါဝင်မှု",
|
||||
"MessageServerConfigurationUpdated": "ဆာဗာဖွဲ့စည်းပုံကို အပ်ဒိတ်လုပ်ပြီးပါပြီ။",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "ဆာဗာဖွဲ့စည်းပုံကဏ္ဍ {0} ကို အပ်ဒိတ်လုပ်ပြီးပါပြီ။",
|
||||
"MessageApplicationUpdatedTo": "Jellyfin ဆာဗာကို {0} သို့ အပ်ဒိတ်လုပ်ထားသည်",
|
||||
"MessageApplicationUpdated": "Jellyfin ဆာဗာကို အပ်ဒိတ်လုပ်ပြီးပါပြီ။",
|
||||
"Latest": "နောက်ဆုံး",
|
||||
"LabelRunningTimeValue": "လည်ပတ်ချိန်- {0}",
|
||||
"LabelIpAddressValue": "IP လိပ်စာ- {0}",
|
||||
"ItemRemovedWithName": "{0} ကို ဒစ်ဂျစ်တိုက်မှ ဖယ်ရှားခဲ့သည်။",
|
||||
"ItemAddedWithName": "{0} ကို စာကြည့်တိုက်သို့ ထည့်ထားသည်။",
|
||||
"Inherit": "ဆက်လက် လုပ်ဆောင်သည်။",
|
||||
"HomeVideos": "ပင်မဗီဒီယိုများ",
|
||||
"HeaderRecordingGroups": "အသံဖမ်းအဖွဲ့များ",
|
||||
"HeaderNextUp": "နောက်ထပ်",
|
||||
"HeaderLiveTV": "Live TV",
|
||||
"HeaderFavoriteSongs": "အကြိုက်ဆုံးသီချင်းများ",
|
||||
"HeaderFavoriteShows": "အကြိုက်ဆုံးရှိုးများ",
|
||||
"HeaderFavoriteEpisodes": "အကြိုက်ဆုံးအပိုင်းများ",
|
||||
"HeaderFavoriteArtists": "အကြိုက်ဆုံးအနုပညာရှင်များ",
|
||||
"HeaderFavoriteAlbums": "အကြိုက်ဆုံး အယ်လ်ဘမ်များ",
|
||||
"HeaderContinueWatching": "ဆက်လက်ကြည့်ရှုပါ။",
|
||||
"HeaderAlbumArtists": "အယ်လ်ဘမ်အနုပညာရှင်များ",
|
||||
"Genres": "အမျိုးအစားများ",
|
||||
"Forced": "အတင်းအကြပ်",
|
||||
"Folders": "ဖိုဒါများ",
|
||||
"Favorites": "အကြိုက်ဆုံးများ",
|
||||
"FailedLoginAttemptWithUserName": "{0} မှ အကောင့်ဝင်ရန် မအောင်မြင်ပါ",
|
||||
"DeviceOnlineWithName": "{0} ကို ချိတ်ဆက်ထားသည်။",
|
||||
"DeviceOfflineWithName": "{0} နှင့် အဆက်ပြတ်သွားပါပြီ။",
|
||||
"ChapterNameValue": "အခန်း {0}",
|
||||
"CameraImageUploadedFrom": "ကင်မရာပုံအသစ်ကို {0} မှ အပ်လုဒ်လုပ်ထားသည်",
|
||||
"AuthenticationSucceededWithUserName": "{0} စစ်မှန်ကြောင်း အောင်မြင်စွာ အတည်ပြုပြီးပါပြီ။",
|
||||
"Application": "အပလီကေးရှင်း",
|
||||
"AppDeviceValues": "အက်ပ်- {0}၊ စက်- {1}"
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user