Compare commits

...

406 Commits

Author SHA1 Message Date
Joshua M. Boniface
a2b51d551f Merge pull request #7115 from joshuaboniface/i-hate-ci-in-code 2022-01-04 20:02:17 -05:00
Joshua M. Boniface
e300b98573 Remove bump-version junk
This didn't work, and won't matter for beta1 so just scrap it.
2022-01-04 19:58:20 -05:00
WWWesten
75a46a1072 Translated using Weblate (Burmese)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/my/
2022-01-04 18:55:29 -05:00
hoanghuy309
6aa37ebb8f Translated using Weblate (Vietnamese)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/vi/
2022-01-04 18:55:29 -05:00
Joshua M. Boniface
072526d798 Merge pull request #7107 from Bond-009/namingtests 2022-01-04 18:17:16 -05:00
Joshua M. Boniface
4da240d731 Merge pull request #7105 from 1337joe/specials-folder-lock 2022-01-04 18:16:57 -05:00
Cody Robibero
b5b4e2ea89 Merge pull request #7109 from crobibero/yep
Use provided SortOrder
2022-01-04 15:53:33 -07:00
Cody Robibero
7d63377194 Use provided SortOrder 2022-01-04 13:07:51 -07:00
Bond_009
71ebe220f0 Speed up Jellyfin.Naming.Tests
In my limited testing this saves ~4 seconds, from 19 to 15 seconds (in
        Xunit itself)
2022-01-04 17:59:30 +01:00
Joe Rogers
3eb4bbbb86 Respect lock state when updating season 0 name 2022-01-04 16:58:14 +01:00
Cody Robibero
c6a1dcf420 Merge pull request #7080 from crobibero/ws-token 2022-01-03 17:48:21 -07:00
Cody Robibero
fddcaf17e1 Merge pull request #7092 from crobibero/orderby-flip 2022-01-03 17:48:11 -07:00
Cody Robibero
b9cd9487bd Merge pull request #7095 from crobibero/gif 2022-01-03 17:48:02 -07:00
Cody Robibero
fcb8ff7c0f Merge pull request #7052 from crobibero/image.jpeg 2022-01-03 17:47:52 -07:00
Cody Robibero
e51777fe4b Merge pull request #7093 from ilaborde/fix-5355 2022-01-03 17:16:04 -07:00
Cody Robibero
6d299eed49 Don't transform gif 2022-01-03 12:23:55 -07:00
ignacio laborde
8952819494 remove unnecessary ToList in DlnaManager 2022-01-03 15:31:08 -03:00
Cody Robibero
f802763e8f Reverse all order-by 2022-01-03 07:59:50 -07:00
Cody Robibero
37061e5b6b Merge pull request #7091 from jellyfin/dependabot/nuget/Serilog.Sinks.Graylog-2.3.0
Bump Serilog.Sinks.Graylog from 2.2.2 to 2.3.0
2022-01-03 06:02:16 -07:00
dependabot[bot]
1c5c0ebb0f Bump Serilog.Sinks.Graylog from 2.2.2 to 2.3.0
Bumps [Serilog.Sinks.Graylog](https://github.com/whir1/serilog-sinks-graylog) from 2.2.2 to 2.3.0.
- [Release notes](https://github.com/whir1/serilog-sinks-graylog/releases)
- [Commits](https://github.com/whir1/serilog-sinks-graylog/commits)

---
updated-dependencies:
- dependency-name: Serilog.Sinks.Graylog
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-03 12:00:59 +00:00
Weevild
0c4b5a5ce8 Translated using Weblate (Swedish)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/sv/
2022-01-02 17:05:58 -05:00
WWWesten
65bf393efb Translated using Weblate (Russian)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ru/
2022-01-02 17:05:58 -05:00
Shadowghost
38d4300309 Translated using Weblate (German)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/de/
2022-01-02 17:05:58 -05:00
Bond-009
11d0c6827f Merge pull request #7058 from cvium/the_most_important_feature 2022-01-02 12:31:20 +01:00
Cody Robibero
0765fd568f Use IAuthorizationContext for websocket 2022-01-01 12:34:22 -07:00
cvium
28c2ac9cc0 Remove file extension filter and fix build 2022-01-01 20:07:03 +01:00
Claus Vium
58b9e5af79 Update Emby.Server.Implementations/Library/Resolvers/GenericVideoResolver.cs
Co-authored-by: Joe Rogers <1337joe@users.noreply.github.com>
2022-01-01 14:52:54 +01:00
Claus Vium
1725b2ee69 Merge pull request #7076 from crobibero/providerid 2021-12-31 21:03:30 +01:00
Cody Robibero
25abe479eb Add ability to search by - 2021-12-31 08:51:50 -07:00
Claus Vium
1b9cb8cca6 Merge pull request #7075 from crobibero/sort 2021-12-31 16:32:26 +01:00
Cody Robibero
c3071f8996 Fix orderby query 2021-12-31 08:03:08 -07:00
Joshua M. Boniface
dc9e1ece29 Merge pull request #7070 from thornbill/fix-ci-builds 2021-12-31 03:47:06 -05:00
Bill Thornton
226a43619f Fix fedora build version 2021-12-30 16:54:59 -05:00
WWWesten
53447976f0 Added translation using Weblate (Burmese) 2021-12-30 16:15:37 -05:00
Bond-009
b2a2bdb088 Merge pull request #7049 from crobibero/warn40219 2021-12-30 13:50:39 +01:00
WWWesten
bb713d1931 Translated using Weblate (Malayalam)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ml/
2021-12-28 10:05:57 -05:00
WWWesten
73a5fc6e26 Translated using Weblate (Thai)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/th/
2021-12-28 10:05:57 -05:00
WWWesten
019b631d7b Translated using Weblate (Urdu (Pakistan))
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ur_PK/
2021-12-28 10:05:57 -05:00
WWWesten
6f4a0683ed Translated using Weblate (Lithuanian)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/lt/
2021-12-28 10:05:57 -05:00
WWWesten
5a1b347b83 Translated using Weblate (Korean)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ko/
2021-12-28 10:05:57 -05:00
WWWesten
828bf1f0ac Translated using Weblate (Hebrew)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/he/
2021-12-28 10:05:57 -05:00
WWWesten
4ec7488bab Translated using Weblate (German (Swiss))
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/gsw/
2021-12-28 10:05:57 -05:00
WWWesten
7ad1527fd0 Translated using Weblate (Greek)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/el/
2021-12-28 10:05:57 -05:00
WWWesten
80fce87f7a Translated using Weblate (Catalan)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ca/
2021-12-28 10:05:57 -05:00
Bond-009
2ab3bf97ef Merge pull request #7059 from crobibero/device-order 2021-12-28 13:56:51 +01:00
Cody Robibero
57db188c2e Fix device ordering 2021-12-27 16:43:29 -07:00
cvium
2749509f00 Use dedicated resolvers for extras 2021-12-28 00:37:40 +01:00
Cody Robibero
7bfc6b5679 Remove more warnings 2021-12-27 07:38:06 -07:00
Cody Robibero
251b9a5235 Merge pull request #7004 from Bond-009/cleanup2 2021-12-27 07:33:11 -07:00
Bond_009
ea8f40e84a More cleanup 2021-12-27 14:20:05 +00:00
Bond-009
4441513ca4 Merge pull request #7055 from crobibero/fix-build-589710 2021-12-27 11:48:51 +00:00
Cody Robibero
ebbde383e8 Fix analysis issues 2021-12-26 11:08:43 -07:00
Cody Robibero
78bb581f0c Merge pull request #6934 from nyanmisaka/hwa
HWA pipeline refactor, AMD/Intel/Nvidia full hardware filtering support, AV1 hwdec
2021-12-26 17:32:06 +00:00
Cody Robibero
a615f87680 Merge pull request #7044 from 1337joe/fix-trailers-v2
Fix trailers v2
2021-12-25 23:31:46 +00:00
Cody Robibero
d594f81f65 Merge pull request #7048 from Bond-009/warn56 2021-12-25 18:46:08 +00:00
Cody Robibero
6804624c15 Add image/jpg to extension lookup 2021-12-25 11:41:39 -07:00
Joe Rogers
cf29aae690 Add rule to pick up theme videos 2021-12-24 21:21:19 +00:00
Joe Rogers
c0ab54f0bd Fix resolved type for Trailers 2021-12-24 21:21:19 +00:00
WWWesten
60e75f7b70 Translated using Weblate (Welsh)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/cy/
2021-12-24 13:05:54 -05:00
itsddpanda
17e6bd5f88 Translated using Weblate (Hindi)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/hi/
2021-12-24 13:05:54 -05:00
WWWesten
42c661e30f Translated using Weblate (Urdu (Pakistan))
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ur_PK/
2021-12-24 13:05:54 -05:00
blob03
9f5873b8c4 Translated using Weblate (French)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/fr/
2021-12-24 13:05:54 -05:00
Bond_009
cbfa355e31 Update StyleCop 2021-12-24 18:28:27 +01:00
nyanmisaka
728a5988b3 Merge remote-tracking branch 'origin/master' into hwa 2021-12-25 00:33:17 +08:00
Bond-009
2e7d173188 Merge pull request #7047 from Bond-009/fixbuild2 2021-12-24 15:42:19 +00:00
Bond_009
ec2645c0c0 Fix build 2021-12-24 16:35:57 +01:00
nyanmisaka
7db753d247 reduce tonemap cpu usage, add deint and AR support in thumbnails
Co-authored-by: Orry Verducci <orry@orryverducci.co.uk>
2021-12-24 17:03:57 +08:00
Claus Vium
d0832c60d4 Merge DynamicHlsController and VideoHlsController (#119)
* Merge DynamicHlsController and VideoHlsController

Co-authored-by: Nyanmisaka <nst799610810@gmail.com>
2021-12-24 17:03:57 +08:00
nyanmisaka
b2d85a02c2 Apply suggestions from code review
Co-authored-by: Cody Robibero <cody@robibe.ro>
Co-authored-by: Claus Vium <cvium@users.noreply.github.com>
Co-authored-by: Bond_009 <bond.009@outlook.com>
2021-12-24 17:03:57 +08:00
nyanmisaka
4b9c84c52e EncodingHelper hwaccel pipelines refactor
separate the HW pipeline according to HWA method for maintainability.
2021-12-24 17:03:57 +08:00
Claus Vium
daa76d3d34 Merge pull request #7042 from crobibero/fix-build 2021-12-24 10:00:43 +01:00
Cody Robibero
599fc9e671 Merge pull request #5836 from BaronGreenback/comparisons 2021-12-24 02:43:18 +00:00
Cody Robibero
17f43c8e01 Update Jellyfin.Api/Controllers/VideoHlsController.cs 2021-12-24 02:42:43 +00:00
Cody Robibero
634ce40c2f Merge branch 'master' into comparisons 2021-12-24 02:41:50 +00:00
Cody Robibero
b5459f49d3 Merge pull request #5009 from MrTimscampi/studios-images-plugin 2021-12-24 02:40:59 +00:00
Cody Robibero
932c2c6665 Fix config.html 2021-12-23 19:40:24 -07:00
Cody Robibero
a04ab6b876 Merge branch 'master' into studios-images-plugin
# Conflicts:
#	MediaBrowser.Providers/MediaBrowser.Providers.csproj
2021-12-23 19:38:10 -07:00
Cody Robibero
a8a8ce4e7b Fix build from PR merging 2021-12-23 19:27:51 -07:00
Cody Robibero
8c7dd0a691 Merge pull request #6819 from cvium/why_should_we_use_the_imageinfo_when_we_can_guess 2021-12-24 02:02:37 +00:00
Cody Robibero
55b429edb7 Merge pull request #6920 from marius-luca-87/subtitle_drop 2021-12-24 02:01:44 +00:00
Cody Robibero
076a13abeb Merge pull request #7029 from cvium/allocations_maybe 2021-12-24 01:59:25 +00:00
Cody Robibero
259bc231d6 Merge pull request #6867 from yresquirol/related-media 2021-12-24 01:59:11 +00:00
Cody Robibero
7774cb2067 Update Jellyfin.Api/Controllers/LibraryController.cs 2021-12-23 20:46:27 +00:00
Cody Robibero
00211ca056 Merge pull request #7028 from cvium/everything_went_wrong 2021-12-22 17:57:00 +00:00
Cody Robibero
ca4769ab68 Merge pull request #7035 from cvium/force_remux_or_transcode 2021-12-21 11:23:02 -07:00
Bond-009
84ac692312 Merge pull request #7034 from cvium/dont_die 2021-12-21 14:57:04 +00:00
cvium
a7a7173cd5 Force a remux/transcode with external audio files 2021-12-21 14:35:58 +01:00
cvium
c86b064f80 Catch HttpRequestException when saving images from local provider 2021-12-21 12:29:09 +01:00
cvium
05c8834a3a Don't cache special feature ids 2021-12-21 00:10:58 +01:00
cvium
9158511017 Don't skip extras refresh when replacing metadata or doing a full refresh 2021-12-20 23:58:09 +01:00
Kichirou Hoshino
afaff1310f Translated using Weblate (Filipino)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/fil/
2021-12-20 13:03:17 -05:00
cvium
b880dc8a4a Use our own Contains extension 2021-12-20 13:31:07 +01:00
cvium
91292b8ea5 Fix build 2021-12-20 12:34:16 +01:00
cvium
83a94aa612 Fix extras folders 2021-12-20 12:15:20 +01:00
Cody Robibero
cb41dda5b3 Merge pull request #7015 from Bond-009/zip 2021-12-19 11:32:12 -07:00
Cody Robibero
a633a3052f Merge pull request #7013 from 1337joe/increment-library-names 2021-12-19 11:32:06 -07:00
Cody Robibero
e8ae501430 Merge pull request #7018 from Bond-009/json2 2021-12-19 11:32:00 -07:00
Cody Robibero
db46eaa744 Merge pull request #7021 from cvium/baseitem_closure 2021-12-19 11:31:54 -07:00
Cody Robibero
71f6326e1a Merge pull request #7012 from nnnlog/remove-protect-clock 2021-12-19 11:13:47 -07:00
cvium
91f3ce3109 Use == instead of Object.Equals to avoid closure allocation 2021-12-19 18:24:05 +01:00
Bond-009
3f3295a5d6 Merge pull request #7020 from cvium/static_anon 2021-12-19 14:42:28 +00:00
cvium
76c2775d8c Use static lambdas 2021-12-19 10:27:57 +01:00
Claus Vium
cd760943a9 Merge pull request #7017 from crobibero/typo 2021-12-19 07:22:19 +01:00
Bond_009
ea9fc9f9cc Remove unreachable branches from JsonConverters
* If the type is a reference type we don't have to handle null ourselves
* reader.ValueSpan is only valid if reader.HasValueSequence is false
2021-12-19 02:20:46 +01:00
Cody Robibero
d9bc65469f Fix query param spelling 2021-12-18 17:17:03 -07:00
Joe Rogers
077f13ae4c Increment library number instead of appending 2021-12-18 20:50:36 +01:00
Bond_009
a4565da4a9 Use System.IO.Compression instead of SharpCompress for zips
Also removes unused methods from ZipClient
2021-12-18 17:52:38 +01:00
WWWesten
c35b704a83 Translated using Weblate (Welsh)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/cy/
2021-12-18 11:05:53 -05:00
Oatavandi
55146334e8 Translated using Weblate (Tamil)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ta/
2021-12-18 11:05:53 -05:00
Cody Robibero
232a148d3f Merge pull request #6978 from Bond-009/framerate
Add tests for ProbeResultNormalizer.GetFrameRate
2021-12-18 07:18:36 -07:00
Bond_009
968c534864 Return null on division by zero 2021-12-18 14:56:10 +01:00
Bond_009
f8fcbc88fc Add tests for ProbeResultNormalizer.GetFrameRate 2021-12-18 14:56:10 +01:00
nlog
92448ffabd Remove ProtectClock for hardware encoding 2021-12-18 13:00:51 +09:00
Cody Robibero
923720c988 Merge pull request #6956 from cvium/what_could_go_wrong 2021-12-17 07:51:32 -07:00
Bond-009
16bf4a013b Merge pull request #7005 from 1337joe/deprecate-enableinternetproviders 2021-12-16 22:49:19 +00:00
Bond-009
b3c48e8e78 Merge pull request #7006 from crobibero/base-item-kind-fixed 2021-12-16 22:43:22 +00:00
Joshua M. Boniface
42a7318fd0 Merge pull request #7000 from brianjmurrell/update-build-for-el7
Fix build on EL7
2021-12-16 17:01:15 -05:00
Cody Robibero
8ddba55214 Use string builder instead of string interpolation 2021-12-16 09:18:51 -07:00
Cody Robibero
0dd304f86c Log warning for unknown BaseItemKind to Type mapping 2021-12-16 07:54:22 -07:00
Cody Robibero
249a1ca955 Add mapping from BaseItemKind to Type.FullName for querying 2021-12-15 17:57:39 -07:00
Joe Rogers
dea5a3f3bc Deprecate LibraryOptions.EnableInternetProviders 2021-12-16 00:52:18 +01:00
WWWesten
b5d4fdb56e Translated using Weblate (Welsh)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/cy/
2021-12-15 13:05:52 -05:00
WWWesten
e34fd15196 Translated using Weblate (Malay)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ms/
2021-12-15 13:05:52 -05:00
Joshua M. Boniface
7405450da2 Merge pull request #7002 from crobibero/dotnet-6.0.1 2021-12-15 10:36:18 -05:00
Claus Vium
9a0618552b Merge branch 'master' into what_could_go_wrong 2021-12-15 08:38:39 +01:00
Claus Vium
4a58582ad5 Merge pull request #6912 from crobibero/filename-tvmaze
Add additional provider id parsing to file name
2021-12-15 08:37:34 +01:00
Claus Vium
4c9bd905c6 Merge pull request #6979 from crobibero/baseitemkind
Use BaseItemKind where possible
2021-12-15 08:28:44 +01:00
Cody Robibero
5e8aaa68cf Update to dotnet 6.0.1 2021-12-14 23:47:07 -07:00
Brian J. Murrell
543b0127b3 Fix build on EL7
Add /usr/local/bin to $PATH.

Update fedora/Makefile with enhancements from jellyfin-web.
2021-12-14 17:50:58 -05:00
Cody Robibero
87439665d7 Use array instead of HashSet 2021-12-14 15:10:40 -07:00
cvium
c3c4dc6839 Review 2021-12-14 23:05:45 +01:00
Claus Vium
e575d815cc Update MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs
Co-authored-by: Joe Rogers <1337joe@users.noreply.github.com>
2021-12-14 22:57:01 +01:00
Cody Robibero
ec9cff29df Merge pull request #6998 from 1337joe/dont-crash-on-missing-server-config
Fix crash on missing server config file
2021-12-14 11:56:44 -07:00
Joe Rogers
0f4da9f635 Fix crash on missing server config file 2021-12-14 19:29:31 +01:00
Cody Robibero
0edf77994a Cache BaseItemKind 2021-12-14 07:41:29 -07:00
Cody Robibero
38f0e611c8 Merge pull request #6994 from Tedyst/master
Fixed crash in MigrationRunner by checking migration for file existance
2021-12-14 05:02:31 -07:00
Stoica Tedy
4e03801931 Update Jellyfin.Server/Migrations/MigrationRunner.cs
Co-authored-by: Claus Vium <cvium@users.noreply.github.com>
2021-12-14 09:57:20 +02:00
Stoica Tedy
250332104b Fixed crash in MigrationRunner
The crashed was caused by importing the migrationOptions even if the
migrations.xml file is non existant.
[Issue]: ~/.config/jellyfin/migrations.xml not found #6992
2021-12-14 09:31:35 +02:00
Bond-009
0872ede57b Merge pull request #6988 from jellyfin/dependabot/nuget/System.Linq.Async-5.1.0 2021-12-13 21:25:21 +00:00
dependabot[bot]
0120d80b78 Bump System.Linq.Async from 5.0.0 to 5.1.0
Bumps [System.Linq.Async](https://github.com/dotnet/reactive) from 5.0.0 to 5.1.0.
- [Release notes](https://github.com/dotnet/reactive/releases)
- [Commits](https://github.com/dotnet/reactive/compare/ixnet-v5.0.0...ixnet-v5.1.0)

---
updated-dependencies:
- dependency-name: System.Linq.Async
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-13 12:00:51 +00:00
cvium
9e665ff520 Fix usage of RegexOptions.Compiled 2021-12-13 08:27:20 +01:00
Claus Vium
5c3d0b5510 Update Emby.Naming/Video/ExtraResolver.cs
Co-authored-by: Bond-009 <bond.009@outlook.com>
2021-12-13 08:22:17 +01:00
cvium
3a4e7fb075 semi undo 2021-12-13 00:26:54 +01:00
cvium
c5569c701c Folder can't have extras 2021-12-12 19:04:22 +01:00
Cody Robibero
32629cd7da Use BaseItemKind where possible 2021-12-12 06:11:27 -07:00
Claus Vium
2c6d6dbbf8 Merge pull request #6976 from Bond-009/float 2021-12-12 07:49:23 +01:00
Claus Vium
bdf9bdb9e6 Merge pull request #6977 from Bond-009/mime 2021-12-12 07:46:16 +01:00
Cody Robibero
0e8c97ed60 Merge pull request #5894 from brianjmurrell/bmurrell/setcap-low-port 2021-12-11 22:12:19 -07:00
Cody Robibero
843ba4506e Merge pull request #6980 from brianjmurrell/ci-autoversion-packages 2021-12-11 22:10:54 -07:00
Brian J. Murrell
3699fa43f0 Merge branch 'jellyfin:master' into ci-autoversion-packages 2021-12-11 23:09:43 -05:00
Brian J. Murrell
296a61cbc4 Run bump_version in make srpm
Also add an "rpms" target that builds the RPMs using mock in a target
environment.

Signed-off-by: Brian J. Murrell <brian@interlinx.bc.ca>
2021-12-11 22:49:19 -05:00
Cody Robibero
a04f8f5efb Fix new test 2021-12-11 20:35:43 -07:00
Cody Robibero
58f788e8ab Update tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs
Co-authored-by: Bond-009 <bond.009@outlook.com>
2021-12-11 19:43:01 -07:00
Bond_009
7ee96a59d3 Use correct jpeg MIME type
image/jpg isn't a valid MIME type
2021-12-12 02:07:35 +01:00
Bond_009
510f92f4c5 Don't check floats for equality 2021-12-12 01:26:47 +01:00
WWWesten
a90614d194 Translated using Weblate (Zulu)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/zu/
2021-12-11 16:05:50 -05:00
INOUE Daisuke
5e8f27d473 Translated using Weblate (Japanese)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ja/
2021-12-11 16:05:50 -05:00
WWWesten
1f02ab83cb Translated using Weblate (Persian)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/fa/
2021-12-11 16:05:50 -05:00
Cody Robibero
2a82f8dc40 Merge pull request #6222 from orryverducci/mbaff-interlace-detection 2021-12-11 13:54:52 -07:00
Joshua M. Boniface
584cf47ef5 Show detailed git status 2021-12-11 15:03:07 -05:00
Joshua M. Boniface
3223ef0e49 Properly handle the -1 tag
Avoids breaking Fedora/CentOS while preserving Debian.
2021-12-11 14:58:20 -05:00
Joshua M. Boniface
3797879d05 Automatically bump stable versions in build
Should prevent strangeness with building these for pre-releases or
actual release versions. Whatever the Git tag is, becomes the package
version.
2021-12-11 14:54:15 -05:00
Cody Robibero
8b4a36d6f7 Merge pull request #6898 from jonas-resch/support-external-audio-files
Add support for external audio files
2021-12-11 12:05:34 -07:00
Cody Robibero
4d1b824651 Merge pull request #6902 from cvium/migrate_networkconfig
Migrate network configuration safely
2021-12-11 12:04:58 -07:00
Cody Robibero
fbc79cb4ce Merge pull request #6965 from bendardenne/patch-1
Add artist to '/' split whitelist
2021-12-11 12:04:39 -07:00
cvium
6d6fb1bba3 Rename unknown parttype to "unknown" 2021-12-11 16:02:51 +01:00
cvium
681bfbd535 Remove duplication 2021-12-10 14:34:46 +01:00
cvium
220443eca1 Simplify StackResolver 2021-12-10 14:23:31 +01:00
WWWesten
4d446eb614 Translated using Weblate (Esperanto)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/eo/
2021-12-10 02:05:47 -05:00
Cody Robibero
d707a201c9 update tests 2021-12-09 12:31:32 -07:00
Marius Luca
7d86ef6f22 - add an option for dropping specific subtitle formats using the DLNA SubtitleProfile 2021-12-09 17:52:51 +02:00
Cody Robibero
eeb8192602 Add StringComparison 2021-12-09 08:38:00 -07:00
Benoît Dardenne
de2d292197 Added artist to '/' split whitelist 2021-12-09 16:07:57 +01:00
Cody Robibero
593b2fd359 Add more speed and more tests 2021-12-09 08:06:06 -07:00
WWWesten
13ac3e3665 Translated using Weblate (Macedonian)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/mk/
2021-12-08 14:05:53 -05:00
WWWesten
5c407dbab6 Translated using Weblate (Romanian)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ro/
2021-12-08 14:05:53 -05:00
Weevild
f50b2f7959 Translated using Weblate (Swedish)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/sv/
2021-12-08 14:05:53 -05:00
archon eleven
1acb9e1d45 Translated using Weblate (Malay)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ms/
2021-12-08 14:05:53 -05:00
WWWesten
6640f3f782 Translated using Weblate (Malay)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ms/
2021-12-08 14:05:53 -05:00
WWWesten
8aef2cb282 Translated using Weblate (Lithuanian)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/lt/
2021-12-08 14:05:53 -05:00
WWWesten
8bd331f5fa Translated using Weblate (Croatian)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/hr/
2021-12-08 14:05:53 -05:00
Muhammed Aljailane
ee80602a0d Translated using Weblate (Arabic)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ar/
2021-12-08 14:05:53 -05:00
Jonas Resch
03b3f08354 Format code in MediaBrowser.Providers/MediaInfo/AudioResolver.cs
Co-authored-by: Claus Vium <cvium@users.noreply.github.com>
2021-12-08 18:55:28 +01:00
Jonas Resch
65833076db Add "Async" suffix to AddExternalAudio method
Co-authored-by: Claus Vium <cvium@users.noreply.github.com>
2021-12-08 16:49:27 +01:00
Jonas Resch
e18d966874 Add "Async" suffix to AddExternalAudio method
Co-authored-by: Claus Vium <cvium@users.noreply.github.com>
2021-12-08 16:49:20 +01:00
Jonas Resch
4cdb590291 Exclude .strm files when searching for external audio files 2021-12-08 10:18:09 +01:00
Jonas Resch
d47811bdaf Fix wrong ffmpeg map argument due to wrong calculation 2021-12-08 10:17:25 +01:00
Jonas Resch
01a0a4a87c Add audioResolver argument to FFProbeVideoInfo initialization 2021-12-08 10:16:48 +01:00
Jonas Resch
87a6fdf847 Merge branch 'support-external-audio-files' of github.com:jonas-resch/jellyfin into support-external-audio-files 2021-12-08 09:55:16 +01:00
Cody Robibero
3513f5a84b Search for attribute text 2021-12-07 17:10:27 -07:00
Claus Vium
a327b43ab7 Update MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs 2021-12-07 20:28:51 +01:00
Claus Vium
3f69eeab27 Merge branch 'master' into support-external-audio-files 2021-12-07 20:16:32 +01:00
Claus Vium
dd8b9e9d23 Merge pull request #6953 from matthiasdv/mdv/harden-systemd-service
Add more hardening to systemd service
2021-12-07 19:46:45 +01:00
cvium
6030946d78 Fixes 2021-12-07 19:23:30 +01:00
cvium
fde84a1e00 Refactor extras parsing 2021-12-07 15:24:57 +01:00
matthiasdv
3176a4ddd9 add more hardening to systemd service 2021-12-06 22:40:00 +01:00
Bond-009
9cafa2cab4 Merge pull request #6946 from jellyfin/dependabot/nuget/libse-3.6.4 2021-12-06 14:46:10 +01:00
dependabot[bot]
c4c7d7431f Bump libse from 3.6.2 to 3.6.4
Bumps [libse](https://github.com/SubtitleEdit/subtitleedit) from 3.6.2 to 3.6.4.
- [Release notes](https://github.com/SubtitleEdit/subtitleedit/releases)
- [Changelog](https://github.com/SubtitleEdit/subtitleedit/blob/master/Changelog.txt)
- [Commits](https://github.com/SubtitleEdit/subtitleedit/compare/3.6.2...3.6.4)

---
updated-dependencies:
- dependency-name: libse
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-06 13:29:10 +00:00
Bond-009
f2648f3eee Merge pull request #6947 from jellyfin/dependabot/nuget/BDInfo-0.7.6.2 2021-12-06 14:28:17 +01:00
dependabot[bot]
29095ab390 Bump BDInfo from 0.7.6.1 to 0.7.6.2
Bumps [BDInfo](https://github.com/jellyfin/BDInfo) from 0.7.6.1 to 0.7.6.2.
- [Release notes](https://github.com/jellyfin/BDInfo/releases)
- [Commits](https://github.com/jellyfin/BDInfo/commits)

---
updated-dependencies:
- dependency-name: BDInfo
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-06 12:01:19 +00:00
WWWesten
b9b3959223 Translated using Weblate (Punjabi)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/pa/
2021-12-06 03:45:54 -05:00
WWWesten
9b71bf8cfe Translated using Weblate (Nepali)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ne/
2021-12-06 03:45:54 -05:00
WWWesten
5eff80fb8c Translated using Weblate (Macedonian)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/mk/
2021-12-06 03:45:54 -05:00
mikixd586
2d77c729f2 Translated using Weblate (Serbian)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/sr/
2021-12-06 03:45:54 -05:00
mio2
9d387c542a Translated using Weblate (Japanese)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ja/
2021-12-06 03:45:54 -05:00
Bas Goos
60f4d70cc5 Translated using Weblate (Dutch)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/nl/
2021-12-06 03:45:54 -05:00
oxixes
bd48b74d5b Translated using Weblate (Spanish)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/es/
2021-12-06 03:45:54 -05:00
Mehyar
838adaea48 Translated using Weblate (Arabic)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ar/
2021-12-06 03:45:54 -05:00
archon eleven
46543ead27 Translated using Weblate (Malay)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ms/
2021-12-06 03:45:54 -05:00
Cody Robibero
707e5bab97 Merge pull request #6941 from holahmeds/mime-type
Use MimeTypes package to determine MIME type
2021-12-04 17:55:24 -07:00
Ahmed Rafiq
cf75f99f0e Update unit test name
Co-authored-by: Claus Vium <cvium@users.noreply.github.com>
2021-12-04 21:14:52 +06:00
Ahmed Rafiq
fa6f6515a6 Update unit test name
Co-authored-by: Claus Vium <cvium@users.noreply.github.com>
2021-12-04 21:14:16 +06:00
Ahmed Rafiq
0e491025ae Refactor MimeTypes.ToExtension()
Co-authored-by: Cody Robibero <cody@robibe.ro>
2021-12-04 21:13:18 +06:00
Ahmed Rafiq
40e05a7993 Update documentation 2021-12-04 21:06:51 +06:00
Ahmed Rafiq
6193fdea69 Use MimeTypes package to determine MIME type
This simplifies the code since we don't have to keep large mappings of extensions and MIME types.

We still keep the ability to override the mappings for:
- filling in entries not present in the package, for e.g. ".azw3"
- picking preferred extensions, for e.g. MimeTypes provides ".conf" as a possible extionsion for "text/plain", and while that is correct, ".txt" would be preferrable
- compatibility reasons
2021-12-04 20:08:16 +06:00
Jonas Resch
ca2d94ee97 Merge branch 'support-external-audio-files' of github.com:jonas-resch/jellyfin into support-external-audio-files 2021-12-03 19:19:53 +01:00
Jonas Resch
99a48554a6 Optimize calculation of external audio stream index in MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
Co-authored-by: Cody Robibero <cody@robibe.ro>
2021-12-03 19:19:22 +01:00
Jonas Resch
120828d8d0 Replace escaped quote string with quote character in MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
Co-authored-by: Cody Robibero <cody@robibe.ro>
2021-12-03 19:18:43 +01:00
Cody Robibero
9cea773d29 Merge pull request #6874 from 1337joe/tmdb-image-size-options 2021-12-03 07:03:02 -07:00
Cody Robibero
a474e4cf3b Merge pull request #6914 from marius-luca-87/dlna 2021-12-02 06:56:07 -07:00
Cody Robibero
b1025121b8 Merge pull request #6929 from Bond-009/jsoncontent 2021-12-02 06:55:28 -07:00
Bond-009
2029076ce8 Merge pull request #6936 from cvium/ffprobeprovider_tweak 2021-12-02 13:42:45 +01:00
cvium
5535b9c01f Reduce allocations 2021-12-02 11:21:59 +01:00
Orry Verducci
e446e9fde9 Merge branch 'master' into mbaff-interlace-detection 2021-12-01 22:13:52 +00:00
Jonas Resch
180e2dc329 Prevent crashes in specific scenarios 2021-12-01 21:05:43 +01:00
Bond_009
40be86eec0 Use PostAsJsonAsync where possible 2021-12-01 15:58:23 +01:00
Bond_009
beafd6eaab Use JsonContent where possible
Should reduce the # of allocated bytes
2021-12-01 15:40:06 +01:00
hoanghuy309
f6d8c19a7a Translated using Weblate (Vietnamese)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/vi/
2021-11-30 17:48:37 -05:00
snieguzary
2da7777e6d Translated using Weblate (Polish)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/pl/
2021-11-30 17:48:37 -05:00
WWWesten
a0df79d8a5 Translated using Weblate (Persian)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/fa/
2021-11-30 17:48:37 -05:00
Bond-009
01b95cf8e6 Merge pull request #6927 from 1337joe/use-ssl-for-tmdb-images 2021-11-30 23:43:26 +01:00
Jonas Resch
6bbfcf1906 Add documentation to AudioResolver class 2021-11-30 21:05:43 +01:00
Jonas Resch
7b50048020 Add ConfigureAwait true in MediaBrowser.Providers/MediaInfo/AudioResolver.cs
Co-authored-by: Claus Vium <cvium@users.noreply.github.com>
2021-11-30 20:45:47 +01:00
Jonas Resch
a9a53dc657 Add ConfigureAwait true in MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
Co-authored-by: Claus Vium <cvium@users.noreply.github.com>
2021-11-30 20:45:21 +01:00
Jonas Resch
0d8170cedb Move variable in MediaBrowser.Providers/MediaInfo/AudioResolver.cs
Co-authored-by: Claus Vium <cvium@users.noreply.github.com>
2021-11-30 20:44:57 +01:00
Jonas Resch
1a35690834 Don't disable warnings in MediaBrowser.Providers/MediaInfo/AudioResolver.cs
Co-authored-by: Claus Vium <cvium@users.noreply.github.com>
2021-11-30 20:44:16 +01:00
Jonas Resch
c61b9ef05a Fix warning due to new line after opening bracket 2021-11-30 19:52:44 +01:00
Jonas Resch
b5b994b22f Fix compiler warning due to missing EnumeratorCancellation attribute 2021-11-30 19:31:46 +01:00
Jonas Resch
0894a6193f Implement coding standards from 2nd code feedback 2021-11-30 19:31:46 +01:00
Jonas Resch
9d34d6339a Change return type from List<string> to IEnumerable<string> in MediaBrowser.Providers/MediaInfo/AudioResolver.cs
Co-authored-by: Claus Vium <cvium@users.noreply.github.com>
2021-11-30 19:31:46 +01:00
Jonas Resch
bbf1399826 Check language for null or empty instead of only null in MediaBrowser.Providers/MediaInfo/AudioResolver.cs
Co-authored-by: Claus Vium <cvium@users.noreply.github.com>
2021-11-30 19:31:46 +01:00
Jonas Resch
d016d483ae Change return type from Task<List<MediaStream>> to Task<IAsyncEnumerable<MediaStream>> in MediaBrowser.Providers/MediaInfo/AudioResolver.cs
Co-authored-by: Claus Vium <cvium@users.noreply.github.com>
2021-11-30 19:31:46 +01:00
Jonas Resch
61b191d345 Fix indentation in MediaBrowser.Providers/MediaInfo/AudioResolver.cs
If statement which checks if filename of audio and video file match or if audio file starts with video filename

Co-authored-by: Claus Vium <cvium@users.noreply.github.com>
2021-11-30 19:31:46 +01:00
Jonas Resch
9433072f90 Only search in video folder for external audio files
Don't search in video metadata folder since audio files won't be stored there

Co-authored-by: Claus Vium <cvium@users.noreply.github.com>
2021-11-30 19:31:46 +01:00
Jonas Resch
a3c5afa443 Add ConfigureAwait false MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
Co-authored-by: Cody Robibero <cody@robibe.ro>
2021-11-30 19:31:46 +01:00
Jonas Resch
f1862f9b1a Add ConfigureAwait false to MediaBrowser.Providers/MediaInfo/AudioResolver.cs
Co-authored-by: Cody Robibero <cody@robibe.ro>
2021-11-30 19:31:46 +01:00
Jonas Resch
a68e58556c Implement code feedback
- Rewrite AudioResolver
- Use async & await instead of .Result
- Add support for audio containers with multiple audio streams (e.g.
  mka)
- Fix bug when using external subtitle and external audio streams at the
  same time
2021-11-30 19:31:46 +01:00
Jonas Resch
5e91f50c43 Update CONTRIBUTORS.md 2021-11-30 19:31:46 +01:00
Jonas Resch
c1a8385c9c Shorten calculation of audio startIndex in MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
Co-authored-by: Claus Vium <cvium@users.noreply.github.com>
2021-11-30 19:31:46 +01:00
Jonas Resch
9978164438 Add support for external audio files 2021-11-30 19:31:46 +01:00
Marius Luca
eaa003775f - take into account the streams dlnaheaders query parameter set by the DidlBuilder NormalizeDlnaMediaUrl function 2021-11-30 16:09:02 +02:00
Joe Rogers
e778462955 Use SSL for tmdb images 2021-11-30 14:31:32 +01:00
Brian J. Murrell
148fcb1bb8 Put low port privilege into an optional subpackage
Move "AmbientCapabilities=CAP_NET_BIND_SERVICE" to the "[Service]"
section of the optional "lowport" unit drop-in file and package that
drop-in in a separate, optionally installable jellyfin-server-lowports
subpackage.

This isolates the CAP_NET_BIND_SERVICE capability to only installations
that desire it.

Signed-off-by: Brian J. Murrell <brian@interlinx.bc.ca>
2021-11-30 01:18:27 -05:00
Brian J. Murrell
757970bfc1 Merge remote-tracking branch 'origin/master' into HEAD 2021-11-29 17:53:26 -05:00
WWWesten
c677b4f6b7 Translated using Weblate (Mongolian)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/mn/
2021-11-29 07:05:47 -05:00
WWWesten
20b0499b00 Translated using Weblate (Telugu)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/te/
2021-11-29 07:05:47 -05:00
rimasx
09f2456919 Translated using Weblate (Estonian)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/et/
2021-11-29 07:05:47 -05:00
WWWesten
bcd84dedda Translated using Weblate (Pirate)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/pr/
2021-11-29 07:05:47 -05:00
WWWesten
2496c5d589 Translated using Weblate (Vietnamese)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/vi/
2021-11-29 07:05:47 -05:00
WWWesten
19c9319d2c Translated using Weblate (Albanian)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/sq/
2021-11-29 07:05:47 -05:00
WWWesten
6ef9ac6e53 Translated using Weblate (Tamil)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ta/
2021-11-29 07:05:47 -05:00
WontTell
502a5fc932 Translated using Weblate (Spanish (Latin America))
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/es_419/
2021-11-29 07:05:47 -05:00
WWWesten
02ce95edaf Translated using Weblate (Serbian)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/sr/
2021-11-29 07:05:47 -05:00
WWWesten
5addb24e9b Translated using Weblate (Portuguese)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/pt/
2021-11-29 07:05:47 -05:00
wolong gl
dc27e7618f Translated using Weblate (Chinese (Simplified))
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/zh_Hans/
2021-11-29 07:05:47 -05:00
Weevild
3e1bbdd3d4 Translated using Weblate (Swedish)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/sv/
2021-11-29 07:05:47 -05:00
WWWesten
2ff786fd3d Translated using Weblate (Malay)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ms/
2021-11-29 07:05:47 -05:00
Bond-009
2a09d4244c Merge pull request #6923 from cvium/query_a_bit_less 2021-11-29 13:00:38 +01:00
cvium
beef6f0855 Don't query series twice 2021-11-28 19:56:31 +01:00
Cody Robibero
80c7119537 Merge pull request #6915 from 1337joe/subtitle-parsing-fix 2021-11-27 16:55:51 -07:00
Cody Robibero
1df56335ee Add more tests 2021-11-27 16:25:21 -07:00
Claus Vium
065d3fa837 performance++ 2021-11-27 16:10:43 -07:00
Joe Rogers
1df5b5034b Address suppressed warnings 2021-11-27 20:35:18 +01:00
Joe Rogers
4a20ae6cb4 Allow default/forced tag without setting language 2021-11-27 20:13:21 +01:00
Claus Vium
a87b87825d Update Jellyfin.Server/Migrations/MigrationRunner.cs
Co-authored-by: Cody Robibero <cody@robibe.ro>
2021-11-27 16:01:27 +01:00
Cody Robibero
976e3160b8 Merge pull request #6907 from marius-luca-87/dlna 2021-11-27 07:47:54 -07:00
Cody Robibero
4890454935 Add additional provider parsing to series file name 2021-11-27 07:44:12 -07:00
Claus Vium
ca887518dd Use the Memory overload for ReadAsync (#6865) 2021-11-27 03:23:46 +01:00
Marius Luca
5b5ae1ef52 - enable seek function when direct streaming over DLNA 2021-11-26 18:09:34 +02:00
WWWesten
3eec137100 Translated using Weblate (Spanish (Latin America))
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/es_419/
2021-11-26 04:09:26 -05:00
WontTell
402a3797a4 Translated using Weblate (Spanish (Latin America))
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/es_419/
2021-11-26 04:09:26 -05:00
cvium
0b871505a6 Remove stray datamember 2021-11-24 14:36:01 +01:00
cvium
0485ff1899 Create a store key constant for network 2021-11-24 13:42:14 +01:00
cvium
69df004b9f Migrate network configuration safely 2021-11-24 13:00:12 +01:00
N4v41
8b9ff893b3 Translated using Weblate (Portuguese)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/pt/
2021-11-24 06:28:00 -05:00
Pedro Almeida
b3d5383073 Translated using Weblate (Portuguese (Portugal))
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/pt_PT/
2021-11-24 06:28:00 -05:00
Csaba
9d5f328a09 Translated using Weblate (Hungarian)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/hu/
2021-11-24 06:27:59 -05:00
WWWesten
f469ee3010 Translated using Weblate (English)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/en/
2021-11-24 06:27:59 -05:00
Bond-009
e30ac91f16 Merge pull request #6900 from ianjazz246/local-time-test 2021-11-24 09:14:22 +01:00
ianjazz246
95183c365a Use local time for RecordingHelperTests 2021-11-23 12:57:05 -08:00
Joe Rogers
0af5e60094 Address review comments
Store null instead of calculating scaled image sizes.
Add endpoint to provide TMDb image size options.
2021-11-22 21:08:07 +01:00
Cody Robibero
3c030740ea Merge pull request #6895 from jellyfin/dependabot/nuget/prometheus-net.AspNetCore-5.0.2
Bump prometheus-net.AspNetCore from 5.0.1 to 5.0.2
2021-11-22 06:50:16 -07:00
dependabot[bot]
bb9ce98379 Bump prometheus-net.AspNetCore from 5.0.1 to 5.0.2
Bumps [prometheus-net.AspNetCore](https://github.com/prometheus-net/prometheus-net) from 5.0.1 to 5.0.2.
- [Release notes](https://github.com/prometheus-net/prometheus-net/releases)
- [Changelog](https://github.com/prometheus-net/prometheus-net/blob/master/History)
- [Commits](https://github.com/prometheus-net/prometheus-net/compare/v5.0.1...v5.0.2)

---
updated-dependencies:
- dependency-name: prometheus-net.AspNetCore
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-22 13:33:18 +00:00
Cody Robibero
02fda265b8 Merge pull request #6894 from jellyfin/dependabot/nuget/prometheus-net-5.0.2
Bump prometheus-net from 5.0.1 to 5.0.2
2021-11-22 06:32:48 -07:00
Cody Robibero
501919df35 Merge pull request #6893 from jellyfin/dependabot/nuget/sharpcompress-0.30.1
Bump sharpcompress from 0.30.0 to 0.30.1
2021-11-22 06:32:35 -07:00
Cody Robibero
107909de30 Merge pull request #6896 from jellyfin/dependabot/nuget/Serilog.Sinks.Console-4.0.1
Bump Serilog.Sinks.Console from 4.0.0 to 4.0.1
2021-11-22 06:32:19 -07:00
dependabot[bot]
052b667d6a Bump Serilog.Sinks.Console from 4.0.0 to 4.0.1
Bumps [Serilog.Sinks.Console](https://github.com/serilog/serilog-sinks-console) from 4.0.0 to 4.0.1.
- [Release notes](https://github.com/serilog/serilog-sinks-console/releases)
- [Commits](https://github.com/serilog/serilog-sinks-console/compare/v4.0.0...v4.0.1)

---
updated-dependencies:
- dependency-name: Serilog.Sinks.Console
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-22 12:01:30 +00:00
dependabot[bot]
e5ea607853 Bump prometheus-net from 5.0.1 to 5.0.2
Bumps [prometheus-net](https://github.com/prometheus-net/prometheus-net) from 5.0.1 to 5.0.2.
- [Release notes](https://github.com/prometheus-net/prometheus-net/releases)
- [Changelog](https://github.com/prometheus-net/prometheus-net/blob/master/History)
- [Commits](https://github.com/prometheus-net/prometheus-net/compare/v5.0.1...v5.0.2)

---
updated-dependencies:
- dependency-name: prometheus-net
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-22 12:01:20 +00:00
dependabot[bot]
223cc8314a Bump sharpcompress from 0.30.0 to 0.30.1
Bumps [sharpcompress](https://github.com/adamhathcock/sharpcompress) from 0.30.0 to 0.30.1.
- [Release notes](https://github.com/adamhathcock/sharpcompress/releases)
- [Commits](https://github.com/adamhathcock/sharpcompress/compare/0.30...0.30.1)

---
updated-dependencies:
- dependency-name: sharpcompress
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-22 12:01:01 +00:00
Claus Vium
39e6658d01 Merge pull request #6879 from crobibero/client-log-cleanup
Remove ClientLog endpoints
2021-11-21 23:57:13 +01:00
Claus Vium
8f051c86f7 Merge pull request #6873 from crobibero/resume-ignore-active
Add ability to exclude active sessions from resumable items
2021-11-21 23:54:17 +01:00
Claus Vium
11419cbbfd Merge pull request #6890 from crobibero/dlna-create
Fix creating DLNA profiles
2021-11-21 20:46:41 +01:00
Cody Robibero
5b7e8a27fb Fix creating DLNA profiles 2021-11-21 11:48:30 -07:00
WWWesten
718e4b8665 Translated using Weblate (Mongolian)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/mn/
2021-11-20 14:05:43 -05:00
hoanghuy309
d387d4df92 Translated using Weblate (Vietnamese)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/vi/
2021-11-20 14:05:43 -05:00
WWWesten
1fa845de6e Translated using Weblate (Ukrainian)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/uk/
2021-11-20 14:05:43 -05:00
WWWesten
7126f9dffc Translated using Weblate (English (United Kingdom))
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/en_GB/
2021-11-20 14:05:43 -05:00
Cody Robibero
b2b4bd82d7 Merge pull request #6862 from 1337joe/query-instead-of-filtering 2021-11-20 08:50:39 -07:00
Cody Robibero
03c7bcf9c6 Merge pull request #6870 from cvium/fix_omdb_image_provider 2021-11-20 08:50:22 -07:00
Cody Robibero
ea355b4262 Remove ClientLog endpoints 2021-11-20 08:47:05 -07:00
cvium
71a0abe211 Remove unnecessary N/A checks (converter changes them to null) 2021-11-20 08:32:07 +01:00
cvium
d8c3b8e7f8 Don't use AppendFormat 2021-11-20 08:24:52 +01:00
Joe Rogers
88baff5693 Apply suggestions from code review
Co-authored-by: Claus Vium <cvium@users.noreply.github.com>
2021-11-19 22:40:42 +01:00
Joe Rogers
6d3b129666 Add image scaling options for tmdb 2021-11-19 22:21:21 +01:00
Cody Robibero
8295a3be0d Add ability to exclude active sessions from resumable items 2021-11-19 14:07:50 -07:00
WWWesten
17a273d237 Update LocalizationManager.cs (#6839) 2021-11-19 18:00:20 +01:00
Cody Robibero
752bb88445 Merge pull request #6871 from cvium/fix_localapiurl 2021-11-19 09:18:25 -07:00
cvium
a263f3978b Only force scheme and port when HTTPS is disallowed 2021-11-19 16:57:48 +01:00
Cody Robibero
0009e5cc27 Merge pull request #6850 from PetrusZ/collection-validator 2021-11-19 07:54:41 -07:00
cvium
96ea865681 Refactor omdb providers 2021-11-19 15:32:07 +01:00
Cody Robibero
4a7498a0be Merge pull request #6869 from cvium/optimize_childcount
Small optimization to child count field
2021-11-19 05:07:54 -07:00
cvium
e3f0a53f59 Small optimization to child count field 2021-11-19 09:15:06 +01:00
Yordany Rodriguez Esquirol
3734c95fd4 Related media according to genre 2021-11-18 15:23:47 -05:00
Joe Rogers
9ba7bf96ef Query MediaSourceManager directly in image providers
Add doc comments/minor tweaks to AudioImageProvider
2021-11-18 15:11:50 +01:00
Joe Rogers
bff5ff0cb8 Merge similar tests with Theories 2021-11-18 14:29:22 +01:00
Petrus.Z
acb86066ff Replace library option to AutomaticallyAddToCollection
Signed-off-by: Petrus.Z <silencly07@gmail.com>
2021-11-18 16:00:14 +08:00
Petrus.Z
263bbf897a Reduce one query
Signed-off-by: Petrus.Z <silencly07@gmail.com>
2021-11-18 15:34:35 +08:00
Joe Rogers
97124f5fce Add missed override 2021-11-18 07:42:04 +01:00
Petrus.Z
6565b0cfbe Fix style issues
Signed-off-by: Petrus.Z <silencly07@gmail.com>
2021-11-18 13:33:44 +08:00
Petrus.Z
b635b5a7e3 Paginate movies query
Signed-off-by: Petrus.Z <silencly07@gmail.com>
2021-11-18 13:08:56 +08:00
Petrus.Z
6d74c83ddb Fix issues mentioned in review, except for option name
Signed-off-by: Petrus.Z <silencly07@gmail.com>
2021-11-18 12:07:27 +08:00
Joe Rogers
7cf5767949 Query media streams by type instead of filtering 2021-11-17 22:34:04 +01:00
Cody Robibero
fa366f0099 Merge pull request #6860 from cvium/fix_xml_endless_loops 2021-11-17 08:39:49 -07:00
Petrus.Z
989013c974 Add new line at end of file
Signed-off-by: Petrus.Z <silencly07@gmail.com>
2021-11-17 22:22:52 +08:00
Petrus.Z
c09e999916 Use List<Gid> instead of List<Movie>
Signed-off-by: Petrus.Z <silencly07@gmail.com>
2021-11-17 22:18:59 +08:00
cvium
61b75c82ce Read past empty elements 2021-11-17 14:49:30 +01:00
rimasx
773a4ae1f3 Translated using Weblate (Estonian)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/et/
2021-11-17 08:48:37 -05:00
WWWesten
38ac84ad71 Translated using Weblate (Esperanto)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/eo/
2021-11-17 08:48:37 -05:00
WWWesten
054ca6a8fb Translated using Weblate (Kazakh)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/kk/
2021-11-17 08:48:37 -05:00
millallo
3d9f865247 Translated using Weblate (Italian)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/it/
2021-11-17 08:48:37 -05:00
Csaba
5cd6fba11a Translated using Weblate (Hungarian)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/hu/
2021-11-17 08:48:37 -05:00
blob03
f7b18d97a7 Translated using Weblate (French)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/fr/
2021-11-17 08:48:37 -05:00
Lukáš Kucharczyk
45e6e2adee Translated using Weblate (Czech)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/cs/
2021-11-17 08:48:37 -05:00
blob03
704c1539a2 Translated using Weblate (English)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/en/
2021-11-17 08:48:37 -05:00
WWWesten
00b293d2e9 Added translation using Weblate (Mongolian) 2021-11-17 08:48:37 -05:00
Petrus.Z
52e9dc66f5 Remove extra blank line
Signed-off-by: Petrus.Z <silencly07@gmail.com>
2021-11-17 21:07:02 +08:00
Petrus.Z
3c8f7d380f Improve performance
Signed-off-by: Petrus.Z <silencly07@gmail.com>
2021-11-17 20:56:14 +08:00
Petrus.Z
0a0ddb0eaf Add AutoCollection option
it can determine whether auto create/add movies to collection

Signed-off-by: Petrus.Z <silencly07@gmail.com>
2021-11-17 14:02:07 +08:00
Cody Robibero
84b8c9c2bc Merge pull request #6857 from Bond-009/warn56 2021-11-16 12:25:54 -07:00
Bond_009
257e1be95f Fix some warnings 2021-11-16 16:31:57 +01:00
rimasx
6003289717 Translated using Weblate (Estonian)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/et/
2021-11-16 09:50:35 -05:00
WWWesten
19d285149b Translated using Weblate (Macedonian)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/mk/
2021-11-16 09:50:35 -05:00
blob03
33a2e1bd50 Translated using Weblate (French)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/fr/
2021-11-16 09:50:35 -05:00
emmanuel billeaud
b8507371d5 Translated using Weblate (French)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/fr/
2021-11-16 09:50:35 -05:00
Bond-009
bddded96ff Merge pull request #6856 from cvium/unused_deps 2021-11-16 15:42:32 +01:00
cvium
24024706bf Fix release build 2021-11-16 12:55:35 +01:00
cvium
93fd1c7075 Fix 2021-11-16 12:27:27 +01:00
cvium
5b1b2621ab Fix build 2021-11-16 12:25:46 +01:00
cvium
b50c3852ef Remove unused dependencies 2021-11-16 12:24:17 +01:00
Petrus.Z
1924d0740d Fix style and performance issues mentioned in review
Signed-off-by: Petrus.Z <silencly07@gmail.com>
2021-11-16 19:03:18 +08:00
Petrus.Z
74459ec403 Fix issues mentioned in review
Signed-off-by: Petrus.Z <silencly07@gmail.com>
2021-11-16 11:50:04 +08:00
Cody Robibero
c32a421ea7 Merge pull request #6851 from cvium/remove_references_to_ilibrarymanager 2021-11-15 15:45:49 -07:00
Cody Robibero
4cfe8fe588 Merge pull request #6831 from 1337joe/image-provider-cleanup 2021-11-15 15:45:33 -07:00
Cody Robibero
06c82973c6 Merge pull request #6854 from 1337joe/add-customprefs-set 2021-11-15 15:45:21 -07:00
Claus Vium
f638cd08ea Merge pull request #6852 from Bond-009/warn54
Fix some warnings
2021-11-15 23:35:53 +01:00
Claus Vium
31b87130a2 Merge pull request #6848 from marius-luca-87/dlna
Ensure the proper StartTimeTicks variable is forwarded to the AddDlnaHeaders function
2021-11-15 23:34:34 +01:00
Joe Rogers
ca99a27611 Add setter for CustomPrefs 2021-11-15 22:03:19 +01:00
Petrus.Z
5eb1fde88c Add Collection Validator, create collection based on nfo
Based on nfo's set element, automatically add movie to collection.

Signed-off-by: Petrus.Z <silencly07@gmail.com>
2021-11-16 00:17:14 +08:00
cvium
24679af2e8 Fix comment 2021-11-15 16:01:34 +01:00
Bond_009
474b035d99 Fix some warnings 2021-11-15 15:57:07 +01:00
cvium
4f45c52674 Remove ILibraryManager as a dependency in resolvers etc. 2021-11-15 15:56:02 +01:00
Joe Rogers
f059be8e4d Add logging and fast return 2021-11-15 15:30:43 +01:00
Cody Robibero
03435641c8 Merge pull request #6849 from 1337joe/server-disabled-metadata 2021-11-15 07:25:56 -07:00
Joe Rogers
58be1d7759 Actually check server disabled metadata providers 2021-11-15 14:47:06 +01:00
Marius Luca
4e0edaf544 - ensure the proper StartTimeTicks variable is forwarded to the AddDlnaHeaders function 2021-11-15 15:34:02 +02:00
rimasx
9c74103fbe Translated using Weblate (Estonian)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/et/
2021-11-15 07:48:24 -05:00
WWWesten
551c6f02a2 Added translation using Weblate (Assamese) 2021-11-15 07:48:24 -05:00
Bond-009
358cf48506 Merge pull request #6847 from jellyfin/dependabot/nuget/Microsoft.SourceLink.GitHub-1.1.1 2021-11-15 13:31:56 +01:00
Bond-009
331d4ad849 Merge pull request #6846 from jellyfin/dependabot/nuget/Diacritics-3.3.10 2021-11-15 13:31:05 +01:00
dependabot[bot]
bd32cecf7a Bump Microsoft.SourceLink.GitHub from 1.1.0 to 1.1.1
Bumps [Microsoft.SourceLink.GitHub](https://github.com/dotnet/sourcelink) from 1.1.0 to 1.1.1.
- [Release notes](https://github.com/dotnet/sourcelink/releases)
- [Commits](https://github.com/dotnet/sourcelink/compare/1.1.0...1.1.1)

---
updated-dependencies:
- dependency-name: Microsoft.SourceLink.GitHub
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-15 12:01:08 +00:00
dependabot[bot]
c84f2e48b0 Bump Diacritics from 3.3.4 to 3.3.10
Bumps [Diacritics](https://github.com/thomasgalliker/Diacritics.NET) from 3.3.4 to 3.3.10.
- [Release notes](https://github.com/thomasgalliker/Diacritics.NET/releases)
- [Commits](https://github.com/thomasgalliker/Diacritics.NET/commits)

---
updated-dependencies:
- dependency-name: Diacritics
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-15 12:00:50 +00:00
Joe Rogers
370b7f8e12 Handle unexpected case more gracefully 2021-11-14 23:13:45 +01:00
Cody Robibero
4cb649853d Merge pull request #6845 from marius-luca-87/dlna
Flush the XmlWriter before calling the StringBuilder ToString() method
2021-11-14 13:02:46 -07:00
Marius Luca
a774d1fa10 - flush the XmlWriter before calling the StringBuilder ToString() method 2021-11-14 20:46:17 +02:00
Haadiy Rozzaq
5254e74719 Translated using Weblate (Indonesian)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/id/
2021-11-14 01:36:37 -05:00
WWWesten
14b5e85461 Translated using Weblate (Belarusian)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/be/
2021-11-14 01:36:37 -05:00
WWWesten
25f1cdbcb5 Translated using Weblate (Catalan)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ca/
2021-11-14 01:36:37 -05:00
Cody Robibero
34df1a030b Merge pull request #6818 from Bond-009/password 2021-11-13 16:56:25 -07:00
Joe Rogers
1d729b2b0f Use codec to determine image format 2021-11-12 16:30:30 +01:00
Joe Rogers
f73a7a6ed8 Use ImageFormat instead of string for extension 2021-11-12 16:22:11 +01:00
Joe Rogers
de9bf327c6 Merge similar tests with Theories 2021-11-12 13:44:48 +01:00
cvium
5d19c26d59 Simplify 2021-11-10 23:46:56 +01:00
cvium
0415d1ccef Reduce indentation 2021-11-10 23:29:41 +01:00
cvium
d10de5b7f9 Try to use Width and Height from ImageInfo to determine aspect ratio 2021-11-10 23:25:01 +01:00
Bond_009
5265b3eee7 Replace PBKDF2-SHA1 with PBKDF2-SHA512
This also migrates already created passwords on login

Source for the number of iterations:
https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#pbkdf2
2021-11-10 22:34:54 +01:00
Orry Verducci
9abe9e7e54 Add rounding to the time base check 2021-10-31 17:04:04 +00:00
Orry Verducci
3a89e88033 Merge remote-tracking branch 'upstream/master' into mbaff-interlace-detection 2021-10-31 10:04:14 +00:00
Orry Verducci
d5b63092ed Add H.264 MBAFF interlace check
Use the codec time base to determine if a MBAFF coded H.264 file is interlaced.
2021-06-24 17:51:35 +01:00
BaronGreenback
6648b7d7da Merge branch 'master' into comparisons 2021-06-19 15:04:30 +01:00
BaronGreenback
97c2c523a8 Merge branch 'master' into comparisons 2021-05-08 17:11:21 +01:00
BaronGreenback
e682c230bd Merge branch 'master' into comparisons 2021-05-05 23:22:54 +01:00
Brian J. Murrell
a3a4689af2 Allow to bind to priveleged ports (i.e. 80/443)
Add "AmbientCapabilities=CAP_NET_BIND_SERVICE" to the "[Service]"
section of the unit file to allow the server to bind to ports 80 and 443.

Signed-off-by: Brian J. Murrell <brian@interlinx.bc.ca>
2021-04-22 10:07:51 -04:00
BaronGreenback
107412f2f2 Update FFProbeVideoInfo.cs 2021-04-19 10:23:05 +01:00
BaronGreenback
6b2b484987 Update SubtitleScheduledTask.cs 2021-04-19 10:22:32 +01:00
BaronGreenback
80877aa945 Cleaned up "value assigned is not used in any execution path" 2021-04-17 09:27:58 +01:00
MrTimscampi
c52a2f2f7b Move studios image providers to plugin 2021-01-12 22:45:28 +01:00
481 changed files with 8953 additions and 7947 deletions

View File

@@ -39,6 +39,10 @@ jobs:
vmImage: 'ubuntu-latest'
steps:
- script: echo "##vso[task.setvariable variable=JellyfinVersion]$( awk -F '/' '{ print $NF }' <<<'$(Build.SourceBranch)' | sed 's/^v//' )"
displayName: Set release version (stable)
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
- script: 'docker build -f deployment/Dockerfile.$(BuildConfiguration) -t jellyfin-server-$(BuildConfiguration) deployment'
displayName: 'Build Dockerfile'
@@ -80,6 +84,10 @@ jobs:
vmImage: 'ubuntu-latest'
steps:
- script: echo "##vso[task.setvariable variable=JellyfinVersion]$( awk -F '/' '{ print $NF }' <<<'$(Build.SourceBranch)' | sed 's/^v//' )"
displayName: Set release version (stable)
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
- task: DownloadPipelineArtifact@2
displayName: 'Download OpenAPI Spec'
inputs:

1
.copr Symbolic link
View File

@@ -0,0 +1 @@
fedora

View File

@@ -1 +0,0 @@
../fedora/Makefile

View File

@@ -150,6 +150,7 @@
- [ianjazz246](https://github.com/ianjazz246)
- [peterspenler](https://github.com/peterspenler)
- [MBR-0001](https://github.com/MBR-0001)
- [jonas-resch](https://github.com/jonas-resch)
# Emby Contributors

View File

@@ -6,7 +6,7 @@
<CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)/jellyfin.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<PropertyGroup>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>

View File

@@ -18,11 +18,8 @@ using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Playlists;
using MediaBrowser.Controller.TV;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Dto;
@@ -30,12 +27,7 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Querying;
using Microsoft.Extensions.Logging;
using Book = MediaBrowser.Controller.Entities.Book;
using Episode = MediaBrowser.Controller.Entities.TV.Episode;
using Genre = MediaBrowser.Controller.Entities.Genre;
using Movie = MediaBrowser.Controller.Entities.Movies.Movie;
using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum;
using Series = MediaBrowser.Controller.Entities.TV.Series;
namespace Emby.Dlna.ContentDirectory
{
@@ -395,6 +387,7 @@ namespace Emby.Dlna.ContentDirectory
}
writer.WriteFullEndElement();
writer.Flush();
xmlWriter.WriteElementString("Result", builder.ToString());
}
@@ -484,6 +477,7 @@ namespace Emby.Dlna.ContentDirectory
}
writer.WriteFullEndElement();
writer.Flush();
xmlWriter.WriteElementString("Result", builder.ToString());
}
@@ -537,7 +531,7 @@ namespace Emby.Dlna.ContentDirectory
User = user,
Recursive = true,
IsMissing = false,
ExcludeItemTypes = new[] { nameof(Book) },
ExcludeItemTypes = new[] { BaseItemKind.Book },
IsFolder = isFolder,
MediaTypes = mediaTypes,
DtoOptions = GetDtoOptions()
@@ -617,7 +611,7 @@ namespace Emby.Dlna.ContentDirectory
Limit = limit,
StartIndex = startIndex,
IsVirtualItem = false,
ExcludeItemTypes = new[] { nameof(Book) },
ExcludeItemTypes = new[] { BaseItemKind.Book },
IsPlaceHolder = false,
DtoOptions = GetDtoOptions(),
OrderBy = GetOrderBy(sort, folder.IsPreSorted)
@@ -642,7 +636,7 @@ namespace Emby.Dlna.ContentDirectory
{
StartIndex = startIndex,
Limit = limit,
IncludeItemTypes = new[] { nameof(LiveTvChannel) },
IncludeItemTypes = new[] { BaseItemKind.LiveTvChannel },
OrderBy = GetOrderBy(sort, false)
};
@@ -673,39 +667,39 @@ namespace Emby.Dlna.ContentDirectory
switch (stubType)
{
case StubType.Latest:
return GetLatest(item, query, nameof(Audio));
return GetLatest(item, query, BaseItemKind.Audio);
case StubType.Playlists:
return GetMusicPlaylists(query);
case StubType.Albums:
return GetChildrenOfItem(item, query, nameof(MusicAlbum));
return GetChildrenOfItem(item, query, BaseItemKind.MusicAlbum);
case StubType.Artists:
return GetMusicArtists(item, query);
case StubType.AlbumArtists:
return GetMusicAlbumArtists(item, query);
case StubType.FavoriteAlbums:
return GetChildrenOfItem(item, query, nameof(MusicAlbum), true);
return GetChildrenOfItem(item, query, BaseItemKind.MusicAlbum, true);
case StubType.FavoriteArtists:
return GetFavoriteArtists(item, query);
case StubType.FavoriteSongs:
return GetChildrenOfItem(item, query, nameof(Audio), true);
return GetChildrenOfItem(item, query, BaseItemKind.Audio, true);
case StubType.Songs:
return GetChildrenOfItem(item, query, nameof(Audio));
return GetChildrenOfItem(item, query, BaseItemKind.Audio);
case StubType.Genres:
return GetMusicGenres(item, query);
}
var serverItems = new ServerItem[]
{
new (item, StubType.Latest),
new (item, StubType.Playlists),
new (item, StubType.Albums),
new (item, StubType.AlbumArtists),
new (item, StubType.Artists),
new (item, StubType.Songs),
new (item, StubType.Genres),
new (item, StubType.FavoriteArtists),
new (item, StubType.FavoriteAlbums),
new (item, StubType.FavoriteSongs)
new(item, StubType.Latest),
new(item, StubType.Playlists),
new(item, StubType.Albums),
new(item, StubType.AlbumArtists),
new(item, StubType.Artists),
new(item, StubType.Songs),
new(item, StubType.Genres),
new(item, StubType.FavoriteArtists),
new(item, StubType.FavoriteAlbums),
new(item, StubType.FavoriteSongs)
};
if (limit < serverItems.Length)
@@ -744,25 +738,25 @@ namespace Emby.Dlna.ContentDirectory
case StubType.ContinueWatching:
return GetMovieContinueWatching(item, query);
case StubType.Latest:
return GetLatest(item, query, nameof(Movie));
return GetLatest(item, query, BaseItemKind.Movie);
case StubType.Movies:
return GetChildrenOfItem(item, query, nameof(Movie));
return GetChildrenOfItem(item, query, BaseItemKind.Movie);
case StubType.Collections:
return GetMovieCollections(query);
case StubType.Favorites:
return GetChildrenOfItem(item, query, nameof(Movie), true);
return GetChildrenOfItem(item, query, BaseItemKind.Movie, true);
case StubType.Genres:
return GetGenres(item, query);
}
var array = new ServerItem[]
{
new (item, StubType.ContinueWatching),
new (item, StubType.Latest),
new (item, StubType.Movies),
new (item, StubType.Collections),
new (item, StubType.Favorites),
new (item, StubType.Genres)
new(item, StubType.ContinueWatching),
new(item, StubType.Latest),
new(item, StubType.Movies),
new(item, StubType.Collections),
new(item, StubType.Favorites),
new(item, StubType.Genres)
};
if (limit < array.Length)
@@ -829,26 +823,26 @@ namespace Emby.Dlna.ContentDirectory
case StubType.NextUp:
return GetNextUp(item, query);
case StubType.Latest:
return GetLatest(item, query, nameof(Episode));
return GetLatest(item, query, BaseItemKind.Episode);
case StubType.Series:
return GetChildrenOfItem(item, query, nameof(Series));
return GetChildrenOfItem(item, query, BaseItemKind.Series);
case StubType.FavoriteSeries:
return GetChildrenOfItem(item, query, nameof(Series), true);
return GetChildrenOfItem(item, query, BaseItemKind.Series, true);
case StubType.FavoriteEpisodes:
return GetChildrenOfItem(item, query, nameof(Episode), true);
return GetChildrenOfItem(item, query, BaseItemKind.Episode, true);
case StubType.Genres:
return GetGenres(item, query);
}
var serverItems = new ServerItem[]
{
new (item, StubType.ContinueWatching),
new (item, StubType.NextUp),
new (item, StubType.Latest),
new (item, StubType.Series),
new (item, StubType.FavoriteSeries),
new (item, StubType.FavoriteEpisodes),
new (item, StubType.Genres)
new(item, StubType.ContinueWatching),
new(item, StubType.NextUp),
new(item, StubType.Latest),
new(item, StubType.Series),
new(item, StubType.FavoriteSeries),
new(item, StubType.FavoriteEpisodes),
new(item, StubType.Genres)
};
if (limit < serverItems.Length)
@@ -896,7 +890,7 @@ namespace Emby.Dlna.ContentDirectory
private QueryResult<ServerItem> GetMovieCollections(InternalItemsQuery query)
{
query.Recursive = true;
query.IncludeItemTypes = new[] { nameof(BoxSet) };
query.IncludeItemTypes = new[] { BaseItemKind.BoxSet };
var result = _libraryManager.GetItemsResult(query);
@@ -911,7 +905,7 @@ namespace Emby.Dlna.ContentDirectory
/// <param name="itemType">The item type.</param>
/// <param name="isFavorite">A value indicating whether to only fetch favorite items.</param>
/// <returns>The <see cref="QueryResult{ServerItem}"/>.</returns>
private QueryResult<ServerItem> GetChildrenOfItem(BaseItem parent, InternalItemsQuery query, string itemType, bool isFavorite = false)
private QueryResult<ServerItem> GetChildrenOfItem(BaseItem parent, InternalItemsQuery query, BaseItemKind itemType, bool isFavorite = false)
{
query.Recursive = true;
query.Parent = parent;
@@ -1011,7 +1005,7 @@ namespace Emby.Dlna.ContentDirectory
private QueryResult<ServerItem> GetMusicPlaylists(InternalItemsQuery query)
{
query.Parent = null;
query.IncludeItemTypes = new[] { nameof(Playlist) };
query.IncludeItemTypes = new[] { BaseItemKind.Playlist };
query.Recursive = true;
var result = _libraryManager.GetItemsResult(query);
@@ -1050,7 +1044,7 @@ namespace Emby.Dlna.ContentDirectory
/// <param name="query">The <see cref="InternalItemsQuery"/>.</param>
/// <param name="itemType">The item type.</param>
/// <returns>The <see cref="QueryResult{ServerItem}"/>.</returns>
private QueryResult<ServerItem> GetLatest(BaseItem parent, InternalItemsQuery query, string itemType)
private QueryResult<ServerItem> GetLatest(BaseItem parent, InternalItemsQuery query, BaseItemKind itemType)
{
query.OrderBy = Array.Empty<(string, SortOrder)>();
@@ -1084,7 +1078,7 @@ namespace Emby.Dlna.ContentDirectory
{
Recursive = true,
ArtistIds = new[] { item.Id },
IncludeItemTypes = new[] { nameof(MusicAlbum) },
IncludeItemTypes = new[] { BaseItemKind.MusicAlbum },
Limit = limit,
StartIndex = startIndex,
DtoOptions = GetDtoOptions(),
@@ -1113,8 +1107,8 @@ namespace Emby.Dlna.ContentDirectory
GenreIds = new[] { item.Id },
IncludeItemTypes = new[]
{
nameof(Movie),
nameof(Series)
BaseItemKind.Movie,
BaseItemKind.Series
},
Limit = limit,
StartIndex = startIndex,
@@ -1142,7 +1136,7 @@ namespace Emby.Dlna.ContentDirectory
{
Recursive = true,
GenreIds = new[] { item.Id },
IncludeItemTypes = new[] { nameof(MusicAlbum) },
IncludeItemTypes = new[] { BaseItemKind.MusicAlbum },
Limit = limit,
StartIndex = startIndex,
DtoOptions = GetDtoOptions(),

View File

@@ -17,8 +17,7 @@ namespace Emby.Dlna.Didl
public Filter(string filter)
{
_all = string.Equals(filter, "*", StringComparison.OrdinalIgnoreCase);
_fields = (filter ?? string.Empty).Split(',', StringSplitOptions.RemoveEmptyEntries);
_fields = filter.Split(',', StringSplitOptions.RemoveEmptyEntries);
}
public bool Contains(string field)

View File

@@ -5,7 +5,6 @@ using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.Json;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
@@ -84,8 +83,7 @@ namespace Emby.Dlna
{
lock (_profiles)
{
var list = _profiles.Values.ToList();
return list
return _profiles.Values
.OrderBy(i => i.Item1.Info.Type == DeviceProfileType.User ? 0 : 1)
.ThenBy(i => i.Item1.Info.Name)
.Select(i => i.Item2)
@@ -227,11 +225,8 @@ namespace Emby.Dlna
{
try
{
var xmlFies = _fileSystem.GetFilePaths(path)
return _fileSystem.GetFilePaths(path)
.Where(i => string.Equals(Path.GetExtension(i), ".xml", StringComparison.OrdinalIgnoreCase))
.ToList();
return xmlFies
.Select(i => ParseProfileFile(i, type))
.Where(i => i != null)
.ToList()!; // We just filtered out all the nulls
@@ -253,11 +248,8 @@ namespace Emby.Dlna
try
{
DeviceProfile profile;
var tempProfile = (DeviceProfile)_xmlSerializer.DeserializeFromFile(typeof(DeviceProfile), path);
profile = ReserializeProfile(tempProfile);
var profile = ReserializeProfile(tempProfile);
profile.Id = path.ToLowerInvariant().GetMD5().ToString("N", CultureInfo.InvariantCulture);
@@ -296,8 +288,7 @@ namespace Emby.Dlna
{
lock (_profiles)
{
var list = _profiles.Values.ToList();
return list
return _profiles.Values
.Select(i => i.Item1)
.OrderBy(i => i.Info.Type == DeviceProfileType.User ? 0 : 1)
.ThenBy(i => i.Info.Name);
@@ -350,7 +341,7 @@ namespace Emby.Dlna
Directory.CreateDirectory(systemProfilesPath);
var fileOptions = AsyncFile.WriteOptions;
fileOptions.Mode = FileMode.CreateNew;
fileOptions.Mode = FileMode.Create;
fileOptions.PreallocationSize = length;
using (var fileStream = new FileStream(path, fileOptions))
{

View File

@@ -22,10 +22,14 @@
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
</PropertyGroup>
<!-- Code Analyzers-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>

View File

@@ -124,7 +124,7 @@ namespace Emby.Dlna.Main
config);
Current = this;
var netConfig = config.GetConfiguration<NetworkConfiguration>("network");
var netConfig = config.GetConfiguration<NetworkConfiguration>(NetworkConfigurationStore.StoreKey);
_disabled = appHost.ListenWithHttps && netConfig.RequireHttps;
if (_disabled && _config.GetDlnaConfiguration().EnableServer)
@@ -262,7 +262,6 @@ namespace Emby.Dlna.Main
{
_publisher = new SsdpDevicePublisher(
_communicationsServer,
_networkManager,
MediaBrowser.Common.System.OperatingSystem.Name,
Environment.OSVersion.VersionString,
_config.GetDlnaConfiguration().SendOnlyMatchedHost)
@@ -400,7 +399,6 @@ namespace Emby.Dlna.Main
_imageProcessor,
_deviceDiscovery,
_httpClientFactory,
_config,
_userDataManager,
_localization,
_mediaSourceManager,

View File

@@ -1179,6 +1179,7 @@ namespace Emby.Dlna.PlayTo
return new Device(deviceProperties, httpClientFactory, logger);
}
#nullable enable
private static DeviceIcon CreateIcon(XElement element)
{
if (element == null)
@@ -1186,69 +1187,61 @@ namespace Emby.Dlna.PlayTo
throw new ArgumentNullException(nameof(element));
}
var mimeType = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("mimetype"));
var width = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("width"));
var height = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("height"));
var depth = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("depth"));
var url = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("url"));
var widthValue = int.Parse(width, NumberStyles.Integer, CultureInfo.InvariantCulture);
var heightValue = int.Parse(height, NumberStyles.Integer, CultureInfo.InvariantCulture);
_ = int.TryParse(width, NumberStyles.Integer, CultureInfo.InvariantCulture, out var widthValue);
_ = int.TryParse(height, NumberStyles.Integer, CultureInfo.InvariantCulture, out var heightValue);
return new DeviceIcon
{
Depth = depth,
Depth = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("depth")) ?? string.Empty,
Height = heightValue,
MimeType = mimeType,
Url = url,
MimeType = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("mimetype")) ?? string.Empty,
Url = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("url")) ?? string.Empty,
Width = widthValue
};
}
private static DeviceService Create(XElement element)
{
var type = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("serviceType"));
var id = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("serviceId"));
var scpdUrl = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("SCPDURL"));
var controlURL = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("controlURL"));
var eventSubURL = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("eventSubURL"));
return new DeviceService
=> new DeviceService()
{
ControlUrl = controlURL,
EventSubUrl = eventSubURL,
ScpdUrl = scpdUrl,
ServiceId = id,
ServiceType = type
ControlUrl = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("controlURL")) ?? string.Empty,
EventSubUrl = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("eventSubURL")) ?? string.Empty,
ScpdUrl = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("SCPDURL")) ?? string.Empty,
ServiceId = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("serviceId")) ?? string.Empty,
ServiceType = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("serviceType")) ?? string.Empty
};
}
private void UpdateMediaInfo(UBaseObject mediaInfo, TransportState state)
private void UpdateMediaInfo(UBaseObject? mediaInfo, TransportState state)
{
TransportState = state;
var previousMediaInfo = CurrentMediaInfo;
CurrentMediaInfo = mediaInfo;
if (previousMediaInfo == null && mediaInfo != null)
if (mediaInfo == null)
{
if (previousMediaInfo != null)
{
OnPlaybackStop(previousMediaInfo);
}
}
else if (previousMediaInfo == null)
{
if (state != TransportState.Stopped)
{
OnPlaybackStart(mediaInfo);
}
}
else if (mediaInfo != null && previousMediaInfo != null && !mediaInfo.Equals(previousMediaInfo))
{
OnMediaChanged(previousMediaInfo, mediaInfo);
}
else if (mediaInfo == null && previousMediaInfo != null)
{
OnPlaybackStop(previousMediaInfo);
}
else if (mediaInfo != null && mediaInfo.Equals(previousMediaInfo))
else if (mediaInfo.Equals(previousMediaInfo))
{
OnPlaybackProgress(mediaInfo);
}
else
{
OnMediaChanged(previousMediaInfo, mediaInfo);
}
}
private void OnPlaybackStart(UBaseObject mediaInfo)

View File

@@ -210,9 +210,9 @@ namespace Emby.Dlna.PlayTo
var mediaSource = await streamInfo.GetMediaSource(CancellationToken.None).ConfigureAwait(false);
var duration = mediaSource == null ?
(_device.Duration == null ? (long?)null : _device.Duration.Value.Ticks) :
mediaSource.RunTimeTicks;
var duration = mediaSource == null
? _device.Duration?.Ticks
: mediaSource.RunTimeTicks;
var playedToCompletion = positionTicks.HasValue && positionTicks.Value == 0;

View File

@@ -11,7 +11,6 @@ using System.Threading.Tasks;
using Jellyfin.Data.Events;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Library;
@@ -35,7 +34,6 @@ namespace Emby.Dlna.PlayTo
private readonly IServerApplicationHost _appHost;
private readonly IImageProcessor _imageProcessor;
private readonly IHttpClientFactory _httpClientFactory;
private readonly IServerConfigurationManager _config;
private readonly IUserDataManager _userDataManager;
private readonly ILocalizationManager _localization;
@@ -47,7 +45,7 @@ namespace Emby.Dlna.PlayTo
private SemaphoreSlim _sessionLock = new SemaphoreSlim(1, 1);
private CancellationTokenSource _disposeCancellationTokenSource = new CancellationTokenSource();
public PlayToManager(ILogger logger, ISessionManager sessionManager, ILibraryManager libraryManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost, IImageProcessor imageProcessor, IDeviceDiscovery deviceDiscovery, IHttpClientFactory httpClientFactory, IServerConfigurationManager config, IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, IMediaEncoder mediaEncoder)
public PlayToManager(ILogger logger, ISessionManager sessionManager, ILibraryManager libraryManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost, IImageProcessor imageProcessor, IDeviceDiscovery deviceDiscovery, IHttpClientFactory httpClientFactory, IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, IMediaEncoder mediaEncoder)
{
_logger = logger;
_sessionManager = sessionManager;
@@ -58,7 +56,6 @@ namespace Emby.Dlna.PlayTo
_imageProcessor = imageProcessor;
_deviceDiscovery = deviceDiscovery;
_httpClientFactory = httpClientFactory;
_config = config;
_userDataManager = userDataManager;
_localization = localization;
_mediaSourceManager = mediaSourceManager;

View File

@@ -175,7 +175,7 @@ namespace Emby.Dlna.PlayTo
var sendValue = state.AllowedValues.FirstOrDefault(a => string.Equals(a, commandParameter, StringComparison.OrdinalIgnoreCase)) ??
(state.AllowedValues.Count > 0 ? state.AllowedValues[0] : value);
return string.Format(CultureInfo.InvariantCulture, "<{0} xmlns:dt=\"urn:schemas-microsoft-com:datatypes\" dt:dt=\"{1}\">{2}</{0}>", argument.Name, state.DataType ?? "string", sendValue);
return string.Format(CultureInfo.InvariantCulture, "<{0} xmlns:dt=\"urn:schemas-microsoft-com:datatypes\" dt:dt=\"{1}\">{2}</{0}>", argument.Name, state.DataType, sendValue);
}
return string.Format(CultureInfo.InvariantCulture, "<{0}>{1}</{0}>", argument.Name, value);

View File

@@ -167,8 +167,7 @@ namespace Emby.Dlna.Profiles
public void AddXmlRootAttribute(string name, string value)
{
var atts = XmlRootAttributes ?? System.Array.Empty<XmlAttribute>();
var list = atts.ToList();
var list = XmlRootAttributes.ToList();
list.Add(new XmlAttribute
{

View File

@@ -189,7 +189,7 @@ namespace Emby.Dlna.Server
builder.Append("<icon>");
builder.Append("<mimetype>")
.Append(SecurityElement.Escape(icon.MimeType ?? string.Empty))
.Append(SecurityElement.Escape(icon.MimeType))
.Append("</mimetype>");
builder.Append("<width>")
.Append(SecurityElement.Escape(icon.Width.ToString(CultureInfo.InvariantCulture)))
@@ -198,7 +198,7 @@ namespace Emby.Dlna.Server
.Append(SecurityElement.Escape(icon.Height.ToString(CultureInfo.InvariantCulture)))
.Append("</height>");
builder.Append("<depth>")
.Append(SecurityElement.Escape(icon.Depth ?? string.Empty))
.Append(SecurityElement.Escape(icon.Depth))
.Append("</depth>");
builder.Append("<url>")
.Append(BuildUrl(icon.Url))
@@ -219,10 +219,10 @@ namespace Emby.Dlna.Server
builder.Append("<service>");
builder.Append("<serviceType>")
.Append(SecurityElement.Escape(service.ServiceType ?? string.Empty))
.Append(SecurityElement.Escape(service.ServiceType))
.Append("</serviceType>");
builder.Append("<serviceId>")
.Append(SecurityElement.Escape(service.ServiceId ?? string.Empty))
.Append(SecurityElement.Escape(service.ServiceId))
.Append("</serviceId>");
builder.Append("<SCPDURL>")
.Append(BuildUrl(service.ScpdUrl))

View File

@@ -38,7 +38,7 @@ namespace Emby.Dlna.Service
builder.Append("<action>");
builder.Append("<name>")
.Append(SecurityElement.Escape(item.Name ?? string.Empty))
.Append(SecurityElement.Escape(item.Name))
.Append("</name>");
builder.Append("<argumentList>");
@@ -48,13 +48,13 @@ namespace Emby.Dlna.Service
builder.Append("<argument>");
builder.Append("<name>")
.Append(SecurityElement.Escape(argument.Name ?? string.Empty))
.Append(SecurityElement.Escape(argument.Name))
.Append("</name>");
builder.Append("<direction>")
.Append(SecurityElement.Escape(argument.Direction ?? string.Empty))
.Append(SecurityElement.Escape(argument.Direction))
.Append("</direction>");
builder.Append("<relatedStateVariable>")
.Append(SecurityElement.Escape(argument.RelatedStateVariable ?? string.Empty))
.Append(SecurityElement.Escape(argument.RelatedStateVariable))
.Append("</relatedStateVariable>");
builder.Append("</argument>");
@@ -81,10 +81,10 @@ namespace Emby.Dlna.Service
.Append("\">");
builder.Append("<name>")
.Append(SecurityElement.Escape(item.Name ?? string.Empty))
.Append(SecurityElement.Escape(item.Name))
.Append("</name>");
builder.Append("<dataType>")
.Append(SecurityElement.Escape(item.DataType ?? string.Empty))
.Append(SecurityElement.Escape(item.DataType))
.Append("</dataType>");
if (item.AllowedValues.Count > 0)

View File

@@ -11,6 +11,10 @@
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
<ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" />
@@ -24,7 +28,7 @@
<!-- Code analysers-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net.Mime;
using System.Text;
using System.Threading.Tasks;
using Jellyfin.Data.Entities;
@@ -101,8 +102,7 @@ namespace Emby.Drawing
public async Task ProcessImage(ImageProcessingOptions options, Stream toStream)
{
var file = await ProcessImage(options).ConfigureAwait(false);
using (var fileStream = AsyncFile.OpenRead(file.Item1))
using (var fileStream = AsyncFile.OpenRead(file.Path))
{
await fileStream.CopyToAsync(toStream).ConfigureAwait(false);
}
@@ -117,7 +117,7 @@ namespace Emby.Drawing
=> _transparentImageTypes.Contains(Path.GetExtension(path));
/// <inheritdoc />
public async Task<(string path, string? mimeType, DateTime dateModified)> ProcessImage(ImageProcessingOptions options)
public async Task<(string Path, string? MimeType, DateTime DateModified)> ProcessImage(ImageProcessingOptions options)
{
ItemImageInfo originalImage = options.Image;
BaseItem item = options.Item;
@@ -130,20 +130,22 @@ namespace Emby.Drawing
originalImageSize = new ImageDimensions(originalImage.Width, originalImage.Height);
}
var mimeType = MimeTypes.GetMimeType(originalImagePath);
if (!_imageEncoder.SupportsImageEncoding)
{
return (originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified);
return (originalImagePath, mimeType, dateModified);
}
var supportedImageInfo = await GetSupportedImage(originalImagePath, dateModified).ConfigureAwait(false);
originalImagePath = supportedImageInfo.path;
originalImagePath = supportedImageInfo.Path;
if (!File.Exists(originalImagePath))
// Original file doesn't exist, or original file is gif.
if (!File.Exists(originalImagePath) || string.Equals(mimeType, MediaTypeNames.Image.Gif, StringComparison.OrdinalIgnoreCase))
{
return (originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified);
return (originalImagePath, mimeType, dateModified);
}
dateModified = supportedImageInfo.dateModified;
dateModified = supportedImageInfo.DateModified;
bool requiresTransparency = _transparentImageTypes.Contains(Path.GetExtension(originalImagePath));
bool autoOrient = false;
@@ -243,7 +245,7 @@ namespace Emby.Drawing
return ImageFormat.Jpg;
}
private string? GetMimeType(ImageFormat format, string path)
private string GetMimeType(ImageFormat format, string path)
=> format switch
{
ImageFormat.Bmp => MimeTypes.GetMimeType("i.bmp"),
@@ -437,7 +439,7 @@ namespace Emby.Drawing
.ToString("N", CultureInfo.InvariantCulture);
}
private async Task<(string path, DateTime dateModified)> GetSupportedImage(string originalImagePath, DateTime dateModified)
private async Task<(string Path, DateTime DateModified)> GetSupportedImage(string originalImagePath, DateTime dateModified)
{
var inputFormat = Path.GetExtension(originalImagePath)
.TrimStart('.')

View File

@@ -14,6 +14,7 @@ namespace Emby.Naming.AudioBook
public class AudioBookListResolver
{
private readonly NamingOptions _options;
private readonly AudioBookResolver _audioBookResolver;
/// <summary>
/// Initializes a new instance of the <see cref="AudioBookListResolver"/> class.
@@ -22,6 +23,7 @@ namespace Emby.Naming.AudioBook
public AudioBookListResolver(NamingOptions options)
{
_options = options;
_audioBookResolver = new AudioBookResolver(_options);
}
/// <summary>
@@ -31,21 +33,18 @@ namespace Emby.Naming.AudioBook
/// <returns>Returns IEnumerable of <see cref="AudioBookInfo"/>.</returns>
public IEnumerable<AudioBookInfo> Resolve(IEnumerable<FileSystemMetadata> files)
{
var audioBookResolver = new AudioBookResolver(_options);
// File with empty fullname will be sorted out here.
var audiobookFileInfos = files
.Select(i => audioBookResolver.Resolve(i.FullName))
.Select(i => _audioBookResolver.Resolve(i.FullName))
.OfType<AudioBookFileInfo>()
.ToList();
var stackResult = new StackResolver(_options)
.ResolveAudioBooks(audiobookFileInfos);
var stackResult = StackResolver.ResolveAudioBooks(audiobookFileInfos);
foreach (var stack in stackResult)
{
var stackFiles = stack.Files
.Select(i => audioBookResolver.Resolve(i))
.Select(i => _audioBookResolver.Resolve(i))
.OfType<AudioBookFileInfo>()
.ToList();

View File

@@ -1,7 +1,7 @@
using System;
using System.IO;
using System.Linq;
using Emby.Naming.Common;
using Jellyfin.Extensions;
namespace Emby.Naming.AudioBook
{
@@ -37,7 +37,7 @@ namespace Emby.Naming.AudioBook
var extension = Path.GetExtension(path);
// Check supported extensions
if (!_options.AudioFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
if (!_options.AudioFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase))
{
return null;
}

View File

@@ -1,6 +1,7 @@
#pragma warning disable CA1819
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using Emby.Naming.Video;
@@ -124,11 +125,11 @@ namespace Emby.Naming.Common
token: "DSR")
};
VideoFileStackingExpressions = new[]
VideoFileStackingRules = new[]
{
"(?<title>.*?)(?<volume>[ _.-]*(?:cd|dvd|p(?:ar)?t|dis[ck])[ _.-]*[0-9]+)(?<ignore>.*?)(?<extension>\\.[^.]+)$",
"(?<title>.*?)(?<volume>[ _.-]*(?:cd|dvd|p(?:ar)?t|dis[ck])[ _.-]*[a-d])(?<ignore>.*?)(?<extension>\\.[^.]+)$",
"(?<title>.*?)(?<volume>[ ._-]*[a-d])(?<ignore>.*?)(?<extension>\\.[^.]+)$"
new FileStackRule(@"^(?<filename>.*?)(?:(?<=[\]\)\}])|[ _.-]+)[\(\[]?(?<parttype>cd|dvd|part|pt|dis[ck])[ _.-]*(?<number>[0-9]+)[\)\]]?(?:\.[^.]+)?$", true),
new FileStackRule(@"^(?<filename>.*?)(?:(?<=[\]\)\}])|[ _.-]+)[\(\[]?(?<parttype>cd|dvd|part|pt|dis[ck])[ _.-]*(?<number>[a-d])[\)\]]?(?:\.[^.]+)?$", false),
new FileStackRule(@"^(?<filename>.*?)(?:(?<=[\]\)\}])|[ _.-]?)(?<number>[a-d])(?:\.[^.]+)?$", false)
};
CleanDateTimes = new[]
@@ -403,6 +404,12 @@ namespace Emby.Naming.Common
VideoExtraRules = new[]
{
new ExtraRule(
ExtraType.Trailer,
ExtraRuleType.DirectoryName,
"trailers",
MediaType.Video),
new ExtraRule(
ExtraType.Trailer,
ExtraRuleType.Filename,
@@ -463,12 +470,24 @@ 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,
@@ -563,7 +582,7 @@ namespace Emby.Naming.Common
ExtraType.Unknown,
ExtraRuleType.DirectoryName,
"extras",
MediaType.Video),
MediaType.Video)
};
Format3DRules = new[]
@@ -675,9 +694,29 @@ namespace Emby.Naming.Common
.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();
}
/// <summary>
/// Gets or sets the folder name to extra types mapping.
/// </summary>
public Dictionary<string, ExtraType> AllExtrasTypesFolderNames { get; set; }
/// <summary>
/// Gets or sets list of audio file extensions.
/// </summary>
@@ -759,9 +798,9 @@ namespace Emby.Naming.Common
public Format3DRule[] Format3DRules { get; set; }
/// <summary>
/// Gets or sets list of raw video file-stacking expressions strings.
/// Gets the file stacking rules.
/// </summary>
public string[] VideoFileStackingExpressions { get; set; }
public FileStackRule[] VideoFileStackingRules { get; }
/// <summary>
/// Gets or sets list of raw clean DateTimes regular expressions strings.
@@ -783,11 +822,6 @@ namespace Emby.Naming.Common
/// </summary>
public ExtraRule[] VideoExtraRules { get; set; }
/// <summary>
/// Gets list of video file-stack regular expressions.
/// </summary>
public Regex[] VideoFileStackingRegexes { get; private set; } = Array.Empty<Regex>();
/// <summary>
/// Gets list of clean datetime regular expressions.
/// </summary>
@@ -813,7 +847,6 @@ namespace Emby.Naming.Common
/// </summary>
public void Compile()
{
VideoFileStackingRegexes = VideoFileStackingExpressions.Select(Compile).ToArray();
CleanDateTimeRegexes = CleanDateTimes.Select(Compile).ToArray();
CleanStringRegexes = CleanStrings.Select(Compile).ToArray();
EpisodeWithoutSeasonRegexes = EpisodeWithoutSeasonExpressions.Select(Compile).ToArray();

View File

@@ -15,6 +15,10 @@
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
</PropertyGroup>
<PropertyGroup Condition=" '$(Stability)'=='Unstable'">
<!-- Include all symbols in the main nupkg until Azure Artifact Feed starts supporting ingesting NuGet symbol packages. -->
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
@@ -38,13 +42,13 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
</ItemGroup>
<!-- Code Analyzers-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>

View File

@@ -2,6 +2,7 @@ using System;
using System.IO;
using System.Linq;
using Emby.Naming.Common;
using Jellyfin.Extensions;
namespace Emby.Naming.Subtitles
{
@@ -34,7 +35,7 @@ namespace Emby.Naming.Subtitles
}
var extension = Path.GetExtension(path);
if (!_options.SubtitleFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
if (!_options.SubtitleFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase))
{
return null;
}
@@ -42,11 +43,11 @@ namespace Emby.Naming.Subtitles
var flags = GetFlags(path);
var info = new SubtitleInfo(
path,
_options.SubtitleDefaultFlags.Any(i => flags.Contains(i, StringComparer.OrdinalIgnoreCase)),
_options.SubtitleForcedFlags.Any(i => flags.Contains(i, StringComparer.OrdinalIgnoreCase)));
_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, StringComparer.OrdinalIgnoreCase)
&& !_options.SubtitleForcedFlags.Contains(i, StringComparer.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

View File

@@ -1,8 +1,8 @@
using System;
using System.IO;
using System.Linq;
using Emby.Naming.Common;
using Emby.Naming.Video;
using Jellyfin.Extensions;
namespace Emby.Naming.TV
{
@@ -48,7 +48,7 @@ namespace Emby.Naming.TV
{
var extension = Path.GetExtension(path);
// Check supported extensions
if (!_options.VideoFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
if (!_options.VideoFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase))
{
// It's not supported. Check stub extensions
if (!StubResolver.TryResolveFile(path, _options, out stubType))

View File

@@ -55,7 +55,7 @@ namespace Emby.Naming.TV
/// <param name="supportSpecialAliases">if set to <c>true</c> [support special aliases].</param>
/// <param name="supportNumericSeasonFolders">if set to <c>true</c> [support numeric season folders].</param>
/// <returns>System.Nullable{System.Int32}.</returns>
private static (int? seasonNumber, bool isSeasonFolder) GetSeasonNumberFromPath(
private static (int? SeasonNumber, bool IsSeasonFolder) GetSeasonNumberFromPath(
string path,
bool supportSpecialAliases,
bool supportNumericSeasonFolders)
@@ -99,7 +99,7 @@ namespace Emby.Naming.TV
if (filename.Contains(name, StringComparison.OrdinalIgnoreCase))
{
var result = GetSeasonNumberFromPathSubstring(filename.Replace(name, " ", StringComparison.OrdinalIgnoreCase));
if (result.seasonNumber.HasValue)
if (result.SeasonNumber.HasValue)
{
return result;
}
@@ -142,7 +142,7 @@ namespace Emby.Naming.TV
/// </summary>
/// <param name="path">The path.</param>
/// <returns>System.Nullable{System.Int32}.</returns>
private static (int? seasonNumber, bool isSeasonFolder) GetSeasonNumberFromPathSubstring(ReadOnlySpan<char> path)
private static (int? SeasonNumber, bool IsSeasonFolder) GetSeasonNumberFromPathSubstring(ReadOnlySpan<char> path)
{
var numericStart = -1;
var length = 0;

View File

@@ -1,4 +1,3 @@
using System.Globalization;
using Emby.Naming.Common;
namespace Emby.Naming.TV
@@ -51,7 +50,7 @@ namespace Emby.Naming.TV
if (expression.IsNamed)
{
result.SeriesName = match.Groups["seriesname"].Value;
result.Success = !string.IsNullOrEmpty(result.SeriesName) && !string.IsNullOrEmpty(match.Groups["seasonnumber"]?.Value);
result.Success = !string.IsNullOrEmpty(result.SeriesName) && !match.Groups["seasonnumber"].ValueSpan.IsEmpty;
}
}

View File

@@ -1,4 +1,3 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Text.RegularExpressions;

View File

@@ -9,45 +9,27 @@ namespace Emby.Naming.Video
/// <summary>
/// Resolve if file is extra for video.
/// </summary>
public class ExtraResolver
public static class ExtraRuleResolver
{
private static readonly char[] _digits = new[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };
private readonly NamingOptions _options;
/// <summary>
/// Initializes a new instance of the <see cref="ExtraResolver"/> class.
/// </summary>
/// <param name="options"><see cref="NamingOptions"/> object containing VideoExtraRules and passed to <see cref="AudioFileParser"/> and <see cref="VideoResolver"/>.</param>
public ExtraResolver(NamingOptions options)
{
_options = options;
}
private static readonly char[] _digits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };
/// <summary>
/// Attempts to resolve if file is extra.
/// </summary>
/// <param name="path">Path to file.</param>
/// <param name="namingOptions">The naming options.</param>
/// <returns>Returns <see cref="ExtraResult"/> object.</returns>
public ExtraResult GetExtraInfo(string path)
public static ExtraResult GetExtraInfo(string path, NamingOptions namingOptions)
{
var result = new ExtraResult();
for (var i = 0; i < _options.VideoExtraRules.Length; i++)
for (var i = 0; i < namingOptions.VideoExtraRules.Length; i++)
{
var rule = _options.VideoExtraRules[i];
if (rule.MediaType == MediaType.Audio)
var rule = namingOptions.VideoExtraRules[i];
if ((rule.MediaType == MediaType.Audio && !AudioFileParser.IsAudioFile(path, namingOptions))
|| (rule.MediaType == MediaType.Video && !VideoResolver.IsVideoFile(path, namingOptions)))
{
if (!AudioFileParser.IsAudioFile(path, _options))
{
continue;
}
}
else if (rule.MediaType == MediaType.Video)
{
if (!VideoResolver.IsVideoFile(path, _options))
{
continue;
}
continue;
}
var pathSpan = path.AsSpan();
@@ -76,9 +58,9 @@ namespace Emby.Naming.Video
{
var filename = Path.GetFileName(path);
var regex = new Regex(rule.Token, RegexOptions.IgnoreCase);
var isMatch = Regex.IsMatch(filename, rule.Token, RegexOptions.IgnoreCase | RegexOptions.Compiled);
if (regex.IsMatch(filename))
if (isMatch)
{
result.ExtraType = rule.ExtraType;
result.Rule = rule;

View File

@@ -1,6 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Jellyfin.Extensions;
namespace Emby.Naming.Video
{
@@ -12,25 +12,30 @@ namespace Emby.Naming.Video
/// <summary>
/// Initializes a new instance of the <see cref="FileStack"/> class.
/// </summary>
public FileStack()
/// <param name="name">The stack name.</param>
/// <param name="isDirectory">Whether the stack files are directories.</param>
/// <param name="files">The stack files.</param>
public FileStack(string name, bool isDirectory, IReadOnlyList<string> files)
{
Files = new List<string>();
Name = name;
IsDirectoryStack = isDirectory;
Files = files;
}
/// <summary>
/// Gets or sets name of file stack.
/// Gets the name of file stack.
/// </summary>
public string Name { get; set; } = string.Empty;
public string Name { get; }
/// <summary>
/// Gets or sets list of paths in stack.
/// Gets the list of paths in stack.
/// </summary>
public List<string> Files { get; set; }
public IReadOnlyList<string> Files { get; }
/// <summary>
/// Gets or sets a value indicating whether stack is directory stack.
/// Gets a value indicating whether stack is directory stack.
/// </summary>
public bool IsDirectoryStack { get; set; }
public bool IsDirectoryStack { get; }
/// <summary>
/// Helper function to determine if path is in the stack.
@@ -40,12 +45,12 @@ namespace Emby.Naming.Video
/// <returns>True if file is in the stack.</returns>
public bool ContainsFile(string file, bool isDirectory)
{
if (IsDirectoryStack == isDirectory)
if (string.IsNullOrEmpty(file))
{
return Files.Contains(file, StringComparer.OrdinalIgnoreCase);
return false;
}
return false;
return IsDirectoryStack == isDirectory && Files.Contains(file, StringComparison.OrdinalIgnoreCase);
}
}
}

View File

@@ -0,0 +1,48 @@
using System.Diagnostics.CodeAnalysis;
using System.Text.RegularExpressions;
namespace Emby.Naming.Video;
/// <summary>
/// Regex based rule for file stacking (eg. disc1, disc2).
/// </summary>
public class FileStackRule
{
private readonly Regex _tokenRegex;
/// <summary>
/// Initializes a new instance of the <see cref="FileStackRule"/> class.
/// </summary>
/// <param name="token">Token.</param>
/// <param name="isNumerical">Whether the file stack rule uses numerical or alphabetical numbering.</param>
public FileStackRule(string token, bool isNumerical)
{
_tokenRegex = new Regex(token, RegexOptions.IgnoreCase);
IsNumerical = isNumerical;
}
/// <summary>
/// Gets a value indicating whether the rule uses numerical or alphabetical numbering.
/// </summary>
public bool IsNumerical { get; }
/// <summary>
/// Match the input against the rule regex.
/// </summary>
/// <param name="input">The input.</param>
/// <param name="result">The part type and number or <c>null</c>.</param>
/// <returns>A value indicating whether the input matched the rule.</returns>
public bool Match(string input, [NotNullWhen(true)] out (string StackName, string PartType, string PartNumber)? result)
{
result = null;
var match = _tokenRegex.Match(input);
if (!match.Success)
{
return false;
}
var partType = match.Groups["parttype"].Success ? match.Groups["parttype"].Value : "unknown";
result = (match.Groups["filename"].Value, partType, match.Groups["number"].Value);
return true;
}
}

View File

@@ -9,7 +9,7 @@ namespace Emby.Naming.Video
public static class Format3DParser
{
// Static default result to save on allocation costs.
private static readonly Format3DResult _defaultResult = new (false, null);
private static readonly Format3DResult _defaultResult = new(false, null);
/// <summary>
/// Parse 3D format related flags.

View File

@@ -2,7 +2,6 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Emby.Naming.AudioBook;
using Emby.Naming.Common;
using MediaBrowser.Model.IO;
@@ -12,37 +11,28 @@ namespace Emby.Naming.Video
/// <summary>
/// Resolve <see cref="FileStack"/> from list of paths.
/// </summary>
public class StackResolver
public static class StackResolver
{
private readonly NamingOptions _options;
/// <summary>
/// Initializes a new instance of the <see cref="StackResolver"/> class.
/// </summary>
/// <param name="options"><see cref="NamingOptions"/> object containing VideoFileStackingRegexes and passes options to <see cref="VideoResolver"/>.</param>
public StackResolver(NamingOptions options)
{
_options = options;
}
/// <summary>
/// Resolves only directories from paths.
/// </summary>
/// <param name="files">List of paths.</param>
/// <param name="namingOptions">The naming options.</param>
/// <returns>Enumerable <see cref="FileStack"/> of directories.</returns>
public IEnumerable<FileStack> ResolveDirectories(IEnumerable<string> files)
public static IEnumerable<FileStack> ResolveDirectories(IEnumerable<string> files, NamingOptions namingOptions)
{
return Resolve(files.Select(i => new FileSystemMetadata { FullName = i, IsDirectory = true }));
return Resolve(files.Select(i => new FileSystemMetadata { FullName = i, IsDirectory = true }), namingOptions);
}
/// <summary>
/// Resolves only files from paths.
/// </summary>
/// <param name="files">List of paths.</param>
/// <param name="namingOptions">The naming options.</param>
/// <returns>Enumerable <see cref="FileStack"/> of files.</returns>
public IEnumerable<FileStack> ResolveFiles(IEnumerable<string> files)
public static IEnumerable<FileStack> ResolveFiles(IEnumerable<string> files, NamingOptions namingOptions)
{
return Resolve(files.Select(i => new FileSystemMetadata { FullName = i, IsDirectory = false }));
return Resolve(files.Select(i => new FileSystemMetadata { FullName = i, IsDirectory = false }), namingOptions);
}
/// <summary>
@@ -50,7 +40,7 @@ namespace Emby.Naming.Video
/// </summary>
/// <param name="files">List of paths.</param>
/// <returns>Enumerable <see cref="FileStack"/> of directories.</returns>
public IEnumerable<FileStack> ResolveAudioBooks(IEnumerable<AudioBookFileInfo> files)
public static IEnumerable<FileStack> ResolveAudioBooks(IEnumerable<AudioBookFileInfo> files)
{
var groupedDirectoryFiles = files.GroupBy(file => Path.GetDirectoryName(file.Path));
@@ -60,19 +50,13 @@ namespace Emby.Naming.Video
{
foreach (var file in directory)
{
var stack = new FileStack { Name = Path.GetFileNameWithoutExtension(file.Path), IsDirectoryStack = false };
stack.Files.Add(file.Path);
var stack = new FileStack(Path.GetFileNameWithoutExtension(file.Path), false, new[] { file.Path });
yield return stack;
}
}
else
{
var stack = new FileStack { Name = Path.GetFileName(directory.Key), IsDirectoryStack = false };
foreach (var file in directory)
{
stack.Files.Add(file.Path);
}
var stack = new FileStack(Path.GetFileName(directory.Key), false, directory.Select(f => f.Path).ToArray());
yield return stack;
}
}
@@ -82,158 +66,91 @@ namespace Emby.Naming.Video
/// Resolves videos from paths.
/// </summary>
/// <param name="files">List of paths.</param>
/// <param name="namingOptions">The naming options.</param>
/// <returns>Enumerable <see cref="FileStack"/> of videos.</returns>
public IEnumerable<FileStack> Resolve(IEnumerable<FileSystemMetadata> files)
public static IEnumerable<FileStack> Resolve(IEnumerable<FileSystemMetadata> files, NamingOptions namingOptions)
{
var list = files
.Where(i => i.IsDirectory || VideoResolver.IsVideoFile(i.FullName, _options) || VideoResolver.IsStubFile(i.FullName, _options))
.OrderBy(i => i.FullName)
.ToList();
var potentialFiles = files
.Where(i => i.IsDirectory || VideoResolver.IsVideoFile(i.FullName, namingOptions) || VideoResolver.IsStubFile(i.FullName, namingOptions))
.OrderBy(i => i.FullName);
var expressions = _options.VideoFileStackingRegexes;
for (var i = 0; i < list.Count; i++)
var potentialStacks = new Dictionary<string, StackMetadata>();
foreach (var file in potentialFiles)
{
var offset = 0;
var file1 = list[i];
var expressionIndex = 0;
while (expressionIndex < expressions.Length)
var name = file.Name;
if (string.IsNullOrEmpty(name))
{
var exp = expressions[expressionIndex];
var stack = new FileStack();
name = Path.GetFileName(file.FullName);
}
// (Title)(Volume)(Ignore)(Extension)
var match1 = FindMatch(file1, exp, offset);
if (match1.Success)
for (var i = 0; i < namingOptions.VideoFileStackingRules.Length; i++)
{
var rule = namingOptions.VideoFileStackingRules[i];
if (!rule.Match(name, out var stackParsingResult))
{
var title1 = match1.Groups["title"].Value;
var volume1 = match1.Groups["volume"].Value;
var ignore1 = match1.Groups["ignore"].Value;
var extension1 = match1.Groups["extension"].Value;
continue;
}
var j = i + 1;
while (j < list.Count)
var stackName = stackParsingResult.Value.StackName;
var partNumber = stackParsingResult.Value.PartNumber;
var partType = stackParsingResult.Value.PartType;
if (!potentialStacks.TryGetValue(stackName, out var stackResult))
{
stackResult = new StackMetadata(file.IsDirectory, rule.IsNumerical, partType);
potentialStacks[stackName] = stackResult;
}
if (stackResult.Parts.Count > 0)
{
if (stackResult.IsDirectory != file.IsDirectory
|| !string.Equals(partType, stackResult.PartType, StringComparison.OrdinalIgnoreCase)
|| stackResult.ContainsPart(partNumber))
{
var file2 = list[j];
if (file1.IsDirectory != file2.IsDirectory)
{
j++;
continue;
}
// (Title)(Volume)(Ignore)(Extension)
var match2 = FindMatch(file2, exp, offset);
if (match2.Success)
{
var title2 = match2.Groups[1].Value;
var volume2 = match2.Groups[2].Value;
var ignore2 = match2.Groups[3].Value;
var extension2 = match2.Groups[4].Value;
if (string.Equals(title1, title2, StringComparison.OrdinalIgnoreCase))
{
if (!string.Equals(volume1, volume2, StringComparison.OrdinalIgnoreCase))
{
if (string.Equals(ignore1, ignore2, StringComparison.OrdinalIgnoreCase)
&& string.Equals(extension1, extension2, StringComparison.OrdinalIgnoreCase))
{
if (stack.Files.Count == 0)
{
stack.Name = title1 + ignore1;
stack.IsDirectoryStack = file1.IsDirectory;
stack.Files.Add(file1.FullName);
}
stack.Files.Add(file2.FullName);
}
else
{
// Sequel
offset = 0;
expressionIndex++;
break;
}
}
else if (!string.Equals(ignore1, ignore2, StringComparison.OrdinalIgnoreCase))
{
// False positive, try again with offset
offset = match1.Groups[3].Index;
break;
}
else
{
// Extension mismatch
offset = 0;
expressionIndex++;
break;
}
}
else
{
// Title mismatch
offset = 0;
expressionIndex++;
break;
}
}
else
{
// No match 2, next expression
offset = 0;
expressionIndex++;
break;
}
j++;
continue;
}
if (j == list.Count)
if (rule.IsNumerical != stackResult.IsNumerical)
{
expressionIndex = expressions.Length;
break;
}
}
else
{
// No match 1
offset = 0;
expressionIndex++;
}
if (stack.Files.Count > 1)
{
yield return stack;
i += stack.Files.Count - 1;
break;
}
stackResult.Parts.Add(partNumber, file);
break;
}
}
}
private static string GetRegexInput(FileSystemMetadata file)
{
// For directories, dummy up an extension otherwise the expressions will fail
var input = !file.IsDirectory
? file.FullName
: file.FullName + ".mkv";
return Path.GetFileName(input);
}
private static Match FindMatch(FileSystemMetadata input, Regex regex, int offset)
{
var regexInput = GetRegexInput(input);
if (offset < 0 || offset >= regexInput.Length)
foreach (var (fileName, stack) in potentialStacks)
{
return Match.Empty;
if (stack.Parts.Count < 2)
{
continue;
}
yield return new FileStack(fileName, stack.IsDirectory, stack.Parts.Select(kv => kv.Value.FullName).ToArray());
}
}
private class StackMetadata
{
public StackMetadata(bool isDirectory, bool isNumerical, string partType)
{
Parts = new Dictionary<string, FileSystemMetadata>(StringComparer.OrdinalIgnoreCase);
IsDirectory = isDirectory;
IsNumerical = isNumerical;
PartType = partType;
}
return regex.Match(regexInput, offset);
public Dictionary<string, FileSystemMetadata> Parts { get; }
public bool IsDirectory { get; }
public bool IsNumerical { get; }
public string PartType { get; }
public bool ContainsPart(string partNumber) => Parts.ContainsKey(partNumber);
}
}
}

View File

@@ -1,7 +1,7 @@
using System;
using System.IO;
using System.Linq;
using Emby.Naming.Common;
using Jellyfin.Extensions;
namespace Emby.Naming.Video
{
@@ -28,7 +28,7 @@ namespace Emby.Naming.Video
var extension = Path.GetExtension(path);
if (!options.StubFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
if (!options.StubFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase))
{
return false;
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using MediaBrowser.Model.Entities;
namespace Emby.Naming.Video
{
@@ -17,7 +18,6 @@ namespace Emby.Naming.Video
Name = name;
Files = Array.Empty<VideoFileInfo>();
Extras = Array.Empty<VideoFileInfo>();
AlternateVersions = Array.Empty<VideoFileInfo>();
}
@@ -39,16 +39,15 @@ namespace Emby.Naming.Video
/// <value>The files.</value>
public IReadOnlyList<VideoFileInfo> Files { get; set; }
/// <summary>
/// Gets or sets the extras.
/// </summary>
/// <value>The extras.</value>
public IReadOnlyList<VideoFileInfo> Extras { get; set; }
/// <summary>
/// Gets or sets the alternate versions.
/// </summary>
/// <value>The alternate versions.</value>
public IReadOnlyList<VideoFileInfo> AlternateVersions { get; set; }
/// <summary>
/// Gets or sets the extra type.
/// </summary>
public ExtraType? ExtraType { get; set; }
}
}

View File

@@ -4,7 +4,6 @@ using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Emby.Naming.Common;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
namespace Emby.Naming.Video
@@ -17,29 +16,41 @@ namespace Emby.Naming.Video
/// <summary>
/// Resolves alternative versions and extras from list of video files.
/// </summary>
/// <param name="files">List of related video files.</param>
/// <param name="videoInfos">List of related video files.</param>
/// <param name="namingOptions">The naming options.</param>
/// <param name="supportMultiVersion">Indication we should consider multi-versions of content.</param>
/// <param name="parseName">Whether to parse the name or use the filename.</param>
/// <returns>Returns enumerable of <see cref="VideoInfo"/> which groups files together when related.</returns>
public static IEnumerable<VideoInfo> Resolve(IEnumerable<FileSystemMetadata> files, NamingOptions namingOptions, bool supportMultiVersion = true)
public static IReadOnlyList<VideoInfo> Resolve(IReadOnlyList<VideoFileInfo> videoInfos, NamingOptions namingOptions, bool supportMultiVersion = true, bool parseName = true)
{
var videoInfos = files
.Select(i => VideoResolver.Resolve(i.FullName, i.IsDirectory, namingOptions))
.OfType<VideoFileInfo>()
.ToList();
// Filter out all extras, otherwise they could cause stacks to not be resolved
// See the unit test TestStackedWithTrailer
var nonExtras = videoInfos
.Where(i => i.ExtraType == null)
.Select(i => new FileSystemMetadata { FullName = i.Path, IsDirectory = i.IsDirectory });
var stackResult = new StackResolver(namingOptions)
.Resolve(nonExtras).ToList();
var stackResult = StackResolver.Resolve(nonExtras, namingOptions).ToList();
var remainingFiles = videoInfos
.Where(i => !stackResult.Any(s => i.Path != null && s.ContainsFile(i.Path, i.IsDirectory)))
.ToList();
var remainingFiles = new List<VideoFileInfo>();
var standaloneMedia = new List<VideoFileInfo>();
for (var i = 0; i < videoInfos.Count; i++)
{
var current = videoInfos[i];
if (stackResult.Any(s => s.ContainsFile(current.Path, current.IsDirectory)))
{
continue;
}
if (current.ExtraType == null)
{
standaloneMedia.Add(current);
}
else
{
remainingFiles.Add(current);
}
}
var list = new List<VideoInfo>();
@@ -47,38 +58,20 @@ namespace Emby.Naming.Video
{
var info = new VideoInfo(stack.Name)
{
Files = stack.Files.Select(i => VideoResolver.Resolve(i, stack.IsDirectoryStack, namingOptions))
Files = stack.Files.Select(i => VideoResolver.Resolve(i, stack.IsDirectoryStack, namingOptions, parseName))
.OfType<VideoFileInfo>()
.ToList()
};
info.Year = info.Files[0].Year;
var extras = ExtractExtras(remainingFiles, stack.Name, Path.GetFileNameWithoutExtension(stack.Files[0].AsSpan()), namingOptions.VideoFlagDelimiters);
if (extras.Count > 0)
{
info.Extras = extras;
}
list.Add(info);
}
var standaloneMedia = remainingFiles
.Where(i => i.ExtraType == null)
.ToList();
foreach (var media in standaloneMedia)
{
var info = new VideoInfo(media.Name) { Files = new[] { media } };
info.Year = info.Files[0].Year;
remainingFiles.Remove(media);
var extras = ExtractExtras(remainingFiles, media.FileNameWithoutExtension, namingOptions.VideoFlagDelimiters);
info.Extras = extras;
list.Add(info);
}
@@ -87,58 +80,12 @@ namespace Emby.Naming.Video
list = GetVideosGroupedByVersion(list, namingOptions);
}
// If there's only one resolved video, use the folder name as well to find extras
if (list.Count == 1)
{
var info = list[0];
var videoPath = list[0].Files[0].Path;
var parentPath = Path.GetDirectoryName(videoPath.AsSpan());
if (!parentPath.IsEmpty)
{
var folderName = Path.GetFileName(parentPath);
if (!folderName.IsEmpty)
{
var extras = ExtractExtras(remainingFiles, folderName, namingOptions.VideoFlagDelimiters);
extras.AddRange(info.Extras);
info.Extras = extras;
}
}
// Add the extras that are just based on file name as well
var extrasByFileName = remainingFiles
.Where(i => i.ExtraRule != null && i.ExtraRule.RuleType == ExtraRuleType.Filename)
.ToList();
remainingFiles = remainingFiles
.Except(extrasByFileName)
.ToList();
extrasByFileName.AddRange(info.Extras);
info.Extras = extrasByFileName;
}
// If there's only one video, accept all trailers
// Be lenient because people use all kinds of mishmash conventions with trailers.
if (list.Count == 1)
{
var trailers = remainingFiles
.Where(i => i.ExtraType == ExtraType.Trailer)
.ToList();
trailers.AddRange(list[0].Extras);
list[0].Extras = trailers;
remainingFiles = remainingFiles
.Except(trailers)
.ToList();
}
// Whatever files are left, just add them
list.AddRange(remainingFiles.Select(i => new VideoInfo(i.Name)
{
Files = new[] { i },
Year = i.Year
Year = i.Year,
ExtraType = i.ExtraType
}));
return list;
@@ -162,6 +109,11 @@ namespace Emby.Naming.Video
for (var i = 0; i < videos.Count; i++)
{
var video = videos[i];
if (video.ExtraType != null)
{
continue;
}
if (!IsEligibleForMultiVersion(folderName, video.Files[0].Path, namingOptions))
{
return videos;
@@ -178,17 +130,14 @@ namespace Emby.Naming.Video
var alternateVersionsLen = videos.Count - 1;
var alternateVersions = new VideoFileInfo[alternateVersionsLen];
var extras = new List<VideoFileInfo>(list[0].Extras);
for (int i = 0; i < alternateVersionsLen; i++)
{
var video = videos[i + 1];
alternateVersions[i] = video.Files[0];
extras.AddRange(video.Extras);
}
list[0].AlternateVersions = alternateVersions;
list[0].Name = folderName.ToString();
list[0].Extras = extras;
return list;
}
@@ -230,7 +179,7 @@ namespace Emby.Naming.Video
var tmpTestFilename = testFilename.ToString();
if (CleanStringParser.TryClean(tmpTestFilename, namingOptions.CleanStringRegexes, out var cleanName))
{
tmpTestFilename = cleanName.Trim().ToString();
tmpTestFilename = cleanName.Trim();
}
// The CleanStringParser should have removed common keywords etc.
@@ -238,67 +187,5 @@ namespace Emby.Naming.Video
|| testFilename[0] == '-'
|| Regex.IsMatch(tmpTestFilename, @"^\[([^]]*)\]", RegexOptions.Compiled);
}
private static ReadOnlySpan<char> TrimFilenameDelimiters(ReadOnlySpan<char> name, ReadOnlySpan<char> videoFlagDelimiters)
{
return name.IsEmpty ? name : name.TrimEnd().TrimEnd(videoFlagDelimiters).TrimEnd();
}
private static bool StartsWith(ReadOnlySpan<char> fileName, ReadOnlySpan<char> baseName, ReadOnlySpan<char> trimmedBaseName)
{
if (baseName.IsEmpty)
{
return false;
}
return fileName.StartsWith(baseName, StringComparison.OrdinalIgnoreCase)
|| (!trimmedBaseName.IsEmpty && fileName.StartsWith(trimmedBaseName, StringComparison.OrdinalIgnoreCase));
}
/// <summary>
/// Finds similar filenames to that of [baseName] and removes any matches from [remainingFiles].
/// </summary>
/// <param name="remainingFiles">The list of remaining filenames.</param>
/// <param name="baseName">The base name to use for the comparison.</param>
/// <param name="videoFlagDelimiters">The video flag delimiters.</param>
/// <returns>A list of video extras for [baseName].</returns>
private static List<VideoFileInfo> ExtractExtras(IList<VideoFileInfo> remainingFiles, ReadOnlySpan<char> baseName, ReadOnlySpan<char> videoFlagDelimiters)
{
return ExtractExtras(remainingFiles, baseName, ReadOnlySpan<char>.Empty, videoFlagDelimiters);
}
/// <summary>
/// Finds similar filenames to that of [firstBaseName] and [secondBaseName] and removes any matches from [remainingFiles].
/// </summary>
/// <param name="remainingFiles">The list of remaining filenames.</param>
/// <param name="firstBaseName">The first base name to use for the comparison.</param>
/// <param name="secondBaseName">The second base name to use for the comparison.</param>
/// <param name="videoFlagDelimiters">The video flag delimiters.</param>
/// <returns>A list of video extras for [firstBaseName] and [secondBaseName].</returns>
private static List<VideoFileInfo> ExtractExtras(IList<VideoFileInfo> remainingFiles, ReadOnlySpan<char> firstBaseName, ReadOnlySpan<char> secondBaseName, ReadOnlySpan<char> videoFlagDelimiters)
{
var trimmedFirstBaseName = TrimFilenameDelimiters(firstBaseName, videoFlagDelimiters);
var trimmedSecondBaseName = TrimFilenameDelimiters(secondBaseName, videoFlagDelimiters);
var result = new List<VideoFileInfo>();
for (var pos = remainingFiles.Count - 1; pos >= 0; pos--)
{
var file = remainingFiles[pos];
if (file.ExtraType == null)
{
continue;
}
var filename = file.FileNameWithoutExtension;
if (StartsWith(filename, firstBaseName, trimmedFirstBaseName)
|| StartsWith(filename, secondBaseName, trimmedSecondBaseName))
{
result.Add(file);
remainingFiles.RemoveAt(pos);
}
}
return result;
}
}
}

View File

@@ -16,10 +16,11 @@ namespace Emby.Naming.Video
/// </summary>
/// <param name="path">The path.</param>
/// <param name="namingOptions">The naming options.</param>
/// <param name="parseName">Whether to parse the name or use the filename.</param>
/// <returns>VideoFileInfo.</returns>
public static VideoFileInfo? ResolveDirectory(string? path, NamingOptions namingOptions)
public static VideoFileInfo? ResolveDirectory(string? path, NamingOptions namingOptions, bool parseName = true)
{
return Resolve(path, true, namingOptions);
return Resolve(path, true, namingOptions, parseName);
}
/// <summary>
@@ -74,7 +75,7 @@ namespace Emby.Naming.Video
var format3DResult = Format3DParser.Parse(path, namingOptions);
var extraResult = new ExtraResolver(namingOptions).GetExtraInfo(path);
var extraResult = ExtraRuleResolver.GetExtraInfo(path, namingOptions);
var name = Path.GetFileNameWithoutExtension(path);

View File

@@ -24,63 +24,63 @@ namespace Emby.Notifications
{
new NotificationTypeInfo
{
Type = NotificationType.ApplicationUpdateInstalled.ToString()
Type = nameof(NotificationType.ApplicationUpdateInstalled)
},
new NotificationTypeInfo
{
Type = NotificationType.InstallationFailed.ToString()
Type = nameof(NotificationType.InstallationFailed)
},
new NotificationTypeInfo
{
Type = NotificationType.PluginInstalled.ToString()
Type = nameof(NotificationType.PluginInstalled)
},
new NotificationTypeInfo
{
Type = NotificationType.PluginError.ToString()
Type = nameof(NotificationType.PluginError)
},
new NotificationTypeInfo
{
Type = NotificationType.PluginUninstalled.ToString()
Type = nameof(NotificationType.PluginUninstalled)
},
new NotificationTypeInfo
{
Type = NotificationType.PluginUpdateInstalled.ToString()
Type = nameof(NotificationType.PluginUpdateInstalled)
},
new NotificationTypeInfo
{
Type = NotificationType.ServerRestartRequired.ToString()
Type = nameof(NotificationType.ServerRestartRequired)
},
new NotificationTypeInfo
{
Type = NotificationType.TaskFailed.ToString()
Type = nameof(NotificationType.TaskFailed)
},
new NotificationTypeInfo
{
Type = NotificationType.NewLibraryContent.ToString()
Type = nameof(NotificationType.NewLibraryContent)
},
new NotificationTypeInfo
{
Type = NotificationType.AudioPlayback.ToString()
Type = nameof(NotificationType.AudioPlayback)
},
new NotificationTypeInfo
{
Type = NotificationType.VideoPlayback.ToString()
Type = nameof(NotificationType.VideoPlayback)
},
new NotificationTypeInfo
{
Type = NotificationType.AudioPlaybackStopped.ToString()
Type = nameof(NotificationType.AudioPlaybackStopped)
},
new NotificationTypeInfo
{
Type = NotificationType.VideoPlaybackStopped.ToString()
Type = nameof(NotificationType.VideoPlaybackStopped)
},
new NotificationTypeInfo
{
Type = NotificationType.UserLockedOut.ToString()
Type = nameof(NotificationType.UserLockedOut)
},
new NotificationTypeInfo
{
Type = NotificationType.ApplicationUpdateAvailable.ToString()
Type = nameof(NotificationType.ApplicationUpdateAvailable)
}
};
@@ -98,7 +98,7 @@ namespace Emby.Notifications
private void Update(NotificationTypeInfo note)
{
note.Name = _localization.GetLocalizedString("NotificationOption" + note.Type) ?? note.Type;
note.Name = _localization.GetLocalizedString("NotificationOption" + note.Type);
note.IsBasedOnUserEvent = note.Type.IndexOf("Playback", StringComparison.OrdinalIgnoreCase) != -1;

View File

@@ -24,7 +24,7 @@
<!-- Code analyzers-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>

View File

@@ -5,6 +5,7 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Data.Events;
using Jellyfin.Extensions;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Entities;
@@ -104,7 +105,7 @@ namespace Emby.Notifications
var type = entry.Type;
if (string.IsNullOrEmpty(type) || !_coreNotificationTypes.Contains(type, StringComparer.OrdinalIgnoreCase))
if (string.IsNullOrEmpty(type) || !_coreNotificationTypes.Contains(type, StringComparison.OrdinalIgnoreCase))
{
return;
}

View File

@@ -26,7 +26,7 @@
<!-- Code Analyzers-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" />
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>

View File

@@ -3,6 +3,7 @@ using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
@@ -60,7 +61,7 @@ namespace Emby.Photos
item.SetImagePath(ImageType.Primary, item.Path);
// Examples: https://github.com/mono/taglib-sharp/blob/a5f6949a53d09ce63ee7495580d6802921a21f14/tests/fixtures/TagLib.Tests.Images/NullOrientationTest.cs
if (_includeExtensions.Contains(Path.GetExtension(item.Path), StringComparer.OrdinalIgnoreCase))
if (_includeExtensions.Contains(Path.GetExtension(item.Path), StringComparison.OrdinalIgnoreCase))
{
try
{

View File

@@ -301,7 +301,7 @@ namespace Emby.Server.Implementations.AppBase
{
return _configurations.GetOrAdd(
key,
(k, configurationManager) =>
static (k, configurationManager) =>
{
var file = configurationManager.GetConfigurationFile(k);
@@ -371,7 +371,7 @@ namespace Emby.Server.Implementations.AppBase
NewConfiguration = configuration
});
_configurations.AddOrUpdate(key, configuration, (k, v) => configuration);
_configurations.AddOrUpdate(key, configuration, (_, _) => configuration);
var path = GetConfigurationFile(key);
Directory.CreateDirectory(Path.GetDirectoryName(path));

View File

@@ -1,6 +1,5 @@
using System;
using System.IO;
using System.Linq;
using MediaBrowser.Model.Serialization;
namespace Emby.Server.Implementations.AppBase

View File

@@ -19,6 +19,7 @@ using Emby.Dlna;
using Emby.Dlna.Main;
using Emby.Dlna.Ssdp;
using Emby.Drawing;
using Emby.Naming.Common;
using Emby.Notifications;
using Emby.Photos;
using Emby.Server.Implementations.Archiving;
@@ -119,7 +120,7 @@ namespace Emby.Server.Implementations
/// <summary>
/// The disposable parts.
/// </summary>
private readonly ConcurrentDictionary<IDisposable, byte> _disposableParts = new ();
private readonly ConcurrentDictionary<IDisposable, byte> _disposableParts = new();
private readonly IFileSystem _fileSystemManager;
private readonly IConfiguration _startupConfig;
@@ -312,22 +313,6 @@ namespace Emby.Server.Implementations
? Environment.MachineName
: ConfigurationManager.Configuration.ServerName;
/// <summary>
/// Temporary function to migration network settings out of system.xml and into network.xml.
/// TODO: remove at the point when a fixed migration path has been decided upon.
/// </summary>
private void MigrateNetworkConfiguration()
{
string path = Path.Combine(ConfigurationManager.CommonApplicationPaths.ConfigurationDirectoryPath, "network.xml");
if (!File.Exists(path))
{
var networkSettings = new NetworkConfiguration();
ClassMigrationHelper.CopyProperties(ConfigurationManager.Configuration, networkSettings);
_xmlSerializer.SerializeToFile(networkSettings, path);
Logger.LogDebug("Successfully migrated network settings.");
}
}
public string ExpandVirtualPath(string path)
{
var appPaths = ApplicationPaths;
@@ -512,8 +497,6 @@ namespace Emby.Server.Implementations
ConfigurationManager.AddParts(GetExports<IConfigurationFactory>());
// Have to migrate settings here as migration subsystem not yet initialised.
MigrateNetworkConfiguration();
NetManager = new NetworkManager(ConfigurationManager, LoggerFactory.CreateLogger<NetworkManager>());
// Initialize runtime stat collection
@@ -596,6 +579,7 @@ namespace Emby.Server.Implementations
serviceCollection.AddTransient(provider => new Lazy<IProviderManager>(provider.GetRequiredService<IProviderManager>));
serviceCollection.AddTransient(provider => new Lazy<IUserViewManager>(provider.GetRequiredService<IUserViewManager>));
serviceCollection.AddSingleton<ILibraryManager, LibraryManager>();
serviceCollection.AddSingleton<NamingOptions>();
serviceCollection.AddSingleton<IMusicManager, MusicManager>();
@@ -1126,12 +1110,12 @@ namespace Emby.Server.Implementations
}
/// <inheritdoc/>
public string GetApiUrlForLocalAccess(bool allowHttps)
public string GetApiUrlForLocalAccess(bool allowHttps = true)
{
// With an empty source, the port will be null
string smart = NetManager.GetBindInterface(string.Empty, out _);
var scheme = allowHttps ? Uri.UriSchemeHttps : Uri.UriSchemeHttp;
var port = allowHttps ? HttpsPort : HttpPort;
var scheme = !allowHttps ? Uri.UriSchemeHttp : null;
int? port = !allowHttps ? HttpPort : null;
return GetLocalApiUrl(smart.Trim('/'), scheme, port);
}

View File

@@ -1,11 +1,8 @@
using System.IO;
using MediaBrowser.Model.IO;
using SharpCompress.Archives.SevenZip;
using SharpCompress.Archives.Tar;
using SharpCompress.Common;
using SharpCompress.Readers;
using SharpCompress.Readers.GZip;
using SharpCompress.Readers.Zip;
namespace Emby.Server.Implementations.Archiving
{
@@ -14,55 +11,6 @@ namespace Emby.Server.Implementations.Archiving
/// </summary>
public class ZipClient : IZipClient
{
/// <summary>
/// Extracts all.
/// </summary>
/// <param name="sourceFile">The source file.</param>
/// <param name="targetPath">The target path.</param>
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
public void ExtractAll(string sourceFile, string targetPath, bool overwriteExistingFiles)
{
using var fileStream = File.OpenRead(sourceFile);
ExtractAll(fileStream, targetPath, overwriteExistingFiles);
}
/// <summary>
/// Extracts all.
/// </summary>
/// <param name="source">The source.</param>
/// <param name="targetPath">The target path.</param>
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
public void ExtractAll(Stream source, string targetPath, bool overwriteExistingFiles)
{
using var reader = ReaderFactory.Open(source);
var options = new ExtractionOptions
{
ExtractFullPath = true
};
if (overwriteExistingFiles)
{
options.Overwrite = true;
}
Directory.CreateDirectory(targetPath);
reader.WriteAllToDirectory(targetPath, options);
}
/// <inheritdoc />
public void ExtractAllFromZip(Stream source, string targetPath, bool overwriteExistingFiles)
{
using var reader = ZipReader.Open(source);
var options = new ExtractionOptions
{
ExtractFullPath = true,
Overwrite = overwriteExistingFiles
};
Directory.CreateDirectory(targetPath);
reader.WriteAllToDirectory(targetPath, options);
}
/// <inheritdoc />
public void ExtractAllFromGz(Stream source, string targetPath, bool overwriteExistingFiles)
{
@@ -94,69 +42,5 @@ namespace Emby.Server.Implementations.Archiving
reader.WriteEntryToFile(Path.Combine(targetPath, filename));
}
}
/// <summary>
/// Extracts all from7z.
/// </summary>
/// <param name="sourceFile">The source file.</param>
/// <param name="targetPath">The target path.</param>
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
public void ExtractAllFrom7z(string sourceFile, string targetPath, bool overwriteExistingFiles)
{
using var fileStream = File.OpenRead(sourceFile);
ExtractAllFrom7z(fileStream, targetPath, overwriteExistingFiles);
}
/// <summary>
/// Extracts all from7z.
/// </summary>
/// <param name="source">The source.</param>
/// <param name="targetPath">The target path.</param>
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
public void ExtractAllFrom7z(Stream source, string targetPath, bool overwriteExistingFiles)
{
using var archive = SevenZipArchive.Open(source);
using var reader = archive.ExtractAllEntries();
var options = new ExtractionOptions
{
ExtractFullPath = true,
Overwrite = overwriteExistingFiles
};
Directory.CreateDirectory(targetPath);
reader.WriteAllToDirectory(targetPath, options);
}
/// <summary>
/// Extracts all from tar.
/// </summary>
/// <param name="sourceFile">The source file.</param>
/// <param name="targetPath">The target path.</param>
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
public void ExtractAllFromTar(string sourceFile, string targetPath, bool overwriteExistingFiles)
{
using var fileStream = File.OpenRead(sourceFile);
ExtractAllFromTar(fileStream, targetPath, overwriteExistingFiles);
}
/// <summary>
/// Extracts all from tar.
/// </summary>
/// <param name="source">The source.</param>
/// <param name="targetPath">The target path.</param>
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
public void ExtractAllFromTar(Stream source, string targetPath, bool overwriteExistingFiles)
{
using var archive = TarArchive.Open(source);
using var reader = archive.ExtractAllEntries();
var options = new ExtractionOptions
{
ExtractFullPath = true,
Overwrite = overwriteExistingFiles
};
Directory.CreateDirectory(targetPath);
reader.WriteAllToDirectory(targetPath, options);
}
}
}

View File

@@ -10,6 +10,7 @@ using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using Jellyfin.Extensions.Json;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Progress;
@@ -129,16 +130,14 @@ namespace Emby.Server.Implementations.Channels
var internalChannel = _libraryManager.GetItemById(item.ChannelId);
if (internalChannel == null)
{
throw new ArgumentException();
throw new ArgumentException(nameof(item.ChannelId));
}
var channel = Channels.FirstOrDefault(i => GetInternalChannelId(i.Name).Equals(internalChannel.Id));
var supportsDelete = channel as ISupportsDelete;
if (supportsDelete == null)
if (channel is not ISupportsDelete supportsDelete)
{
throw new ArgumentException();
throw new ArgumentException(nameof(channel));
}
return supportsDelete.DeleteItem(item.ExternalId, CancellationToken.None);
@@ -179,7 +178,7 @@ namespace Emby.Server.Implementations.Channels
try
{
return (GetChannelProvider(i) is IHasFolderAttributes hasAttributes
&& hasAttributes.Attributes.Contains("Recordings", StringComparer.OrdinalIgnoreCase)) == val;
&& hasAttributes.Attributes.Contains("Recordings", StringComparison.OrdinalIgnoreCase)) == val;
}
catch
{
@@ -541,7 +540,7 @@ namespace Emby.Server.Implementations.Channels
return _libraryManager.GetItemIds(
new InternalItemsQuery
{
IncludeItemTypes = new[] { nameof(Channel) },
IncludeItemTypes = new[] { BaseItemKind.Channel },
OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) }
}).Select(i => GetChannelFeatures(i)).ToArray();
}
@@ -1075,14 +1074,6 @@ namespace Emby.Server.Implementations.Channels
forceUpdate = true;
}
// was used for status
// if (!string.Equals(item.ExternalEtag ?? string.Empty, info.Etag ?? string.Empty, StringComparison.Ordinal))
// {
// item.ExternalEtag = info.Etag;
// forceUpdate = true;
// _logger.LogDebug("Forcing update due to ExternalEtag {0}", item.Name);
// }
if (!internalChannelId.Equals(item.ChannelId))
{
forceUpdate = true;
@@ -1143,7 +1134,7 @@ namespace Emby.Server.Implementations.Channels
if (!info.IsLiveStream)
{
if (item.Tags.Contains("livestream", StringComparer.OrdinalIgnoreCase))
if (item.Tags.Contains("livestream", StringComparison.OrdinalIgnoreCase))
{
item.Tags = item.Tags.Except(new[] { "livestream" }, StringComparer.OrdinalIgnoreCase).ToArray();
_logger.LogDebug("Forcing update due to Tags {0}", item.Name);
@@ -1152,7 +1143,7 @@ namespace Emby.Server.Implementations.Channels
}
else
{
if (!item.Tags.Contains("livestream", StringComparer.OrdinalIgnoreCase))
if (!item.Tags.Contains("livestream", StringComparison.OrdinalIgnoreCase))
{
item.Tags = item.Tags.Concat(new[] { "livestream" }).ToArray();
_logger.LogDebug("Forcing update due to Tags {0}", item.Name);

View File

@@ -2,6 +2,7 @@ using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
@@ -51,7 +52,7 @@ namespace Emby.Server.Implementations.Channels
var uninstalledChannels = _libraryManager.GetItemList(new InternalItemsQuery
{
IncludeItemTypes = new[] { nameof(Channel) },
IncludeItemTypes = new[] { BaseItemKind.Channel },
ExcludeItemIds = installedChannelIds.ToArray()
});

View File

@@ -140,7 +140,7 @@ namespace Emby.Server.Implementations.Collections
if (parentFolder == null)
{
throw new ArgumentException();
throw new ArgumentException(nameof(parentFolder));
}
var path = Path.Combine(parentFolder.Path, folderName);

View File

@@ -1,9 +1,11 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Security.Cryptography;
using System.Text;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Model.Cryptography;
using static MediaBrowser.Common.Cryptography.Constants;
using static MediaBrowser.Model.Cryptography.Constants;
namespace Emby.Server.Implementations.Cryptography
{
@@ -12,10 +14,7 @@ namespace Emby.Server.Implementations.Cryptography
/// </summary>
public class CryptographyProvider : ICryptoProvider
{
// FIXME: When we get DotNet Standard 2.1 we need to revisit how we do the crypto
// Currently supported hash methods from https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptoconfig?view=netcore-2.1
// there might be a better way to autogenerate this list as dotnet updates, but I couldn't find one
// Please note the default method of PBKDF2 is not included, it cannot be used to generate hashes cleanly as it is actually a pbkdf with sha1
// TODO: remove when not needed for backwards compat
private static readonly HashSet<string> _supportedHashMethods = new HashSet<string>()
{
"MD5",
@@ -35,60 +34,81 @@ namespace Emby.Server.Implementations.Cryptography
};
/// <inheritdoc />
public string DefaultHashMethod => "PBKDF2";
public string DefaultHashMethod => "PBKDF2-SHA512";
/// <inheritdoc />
public IEnumerable<string> GetSupportedHashMethods()
=> _supportedHashMethods;
private byte[] PBKDF2(string method, byte[] bytes, byte[] salt, int iterations)
public PasswordHash CreatePasswordHash(ReadOnlySpan<char> password)
{
// downgrading for now as we need this library to be dotnetstandard compliant
// with this downgrade we'll add a check to make sure we're on the downgrade method at the moment
if (method != DefaultHashMethod)
{
throw new CryptographicException($"Cannot currently use PBKDF2 with requested hash method: {method}");
}
using var r = new Rfc2898DeriveBytes(bytes, salt, iterations);
return r.GetBytes(32);
byte[] salt = GenerateSalt();
return new PasswordHash(
DefaultHashMethod,
Rfc2898DeriveBytes.Pbkdf2(
password,
salt,
DefaultIterations,
HashAlgorithmName.SHA512,
DefaultOutputLength),
salt,
new Dictionary<string, string>
{
{ "iterations", DefaultIterations.ToString(CultureInfo.InvariantCulture) }
});
}
/// <inheritdoc />
public byte[] ComputeHash(string hashMethod, byte[] bytes, byte[] salt)
public bool Verify(PasswordHash hash, ReadOnlySpan<char> password)
{
if (hashMethod == DefaultHashMethod)
if (string.Equals(hash.Id, "PBKDF2", StringComparison.Ordinal))
{
return PBKDF2(hashMethod, bytes, salt, DefaultIterations);
return hash.Hash.SequenceEqual(
Rfc2898DeriveBytes.Pbkdf2(
password,
hash.Salt,
int.Parse(hash.Parameters["iterations"], CultureInfo.InvariantCulture),
HashAlgorithmName.SHA1,
32));
}
if (!_supportedHashMethods.Contains(hashMethod))
if (string.Equals(hash.Id, "PBKDF2-SHA512", StringComparison.Ordinal))
{
throw new CryptographicException($"Requested hash method is not supported: {hashMethod}");
return hash.Hash.SequenceEqual(
Rfc2898DeriveBytes.Pbkdf2(
password,
hash.Salt,
int.Parse(hash.Parameters["iterations"], CultureInfo.InvariantCulture),
HashAlgorithmName.SHA512,
DefaultOutputLength));
}
using var h = HashAlgorithm.Create(hashMethod) ?? throw new ResourceNotFoundException($"Unknown hash method: {hashMethod}.");
if (salt.Length == 0)
if (!_supportedHashMethods.Contains(hash.Id))
{
return h.ComputeHash(bytes);
throw new CryptographicException($"Requested hash method is not supported: {hash.Id}");
}
byte[] salted = new byte[bytes.Length + salt.Length];
using var h = HashAlgorithm.Create(hash.Id) ?? throw new ResourceNotFoundException($"Unknown hash method: {hash.Id}.");
var bytes = Encoding.UTF8.GetBytes(password.ToArray());
if (hash.Salt.Length == 0)
{
return hash.Hash.SequenceEqual(h.ComputeHash(bytes));
}
byte[] salted = new byte[bytes.Length + hash.Salt.Length];
Array.Copy(bytes, salted, bytes.Length);
Array.Copy(salt, 0, salted, bytes.Length, salt.Length);
return h.ComputeHash(salted);
hash.Salt.CopyTo(salted.AsSpan(bytes.Length));
return hash.Hash.SequenceEqual(h.ComputeHash(salted));
}
/// <inheritdoc />
public byte[] ComputeHashWithDefaultMethod(byte[] bytes, byte[] salt)
=> PBKDF2(DefaultHashMethod, bytes, salt, DefaultIterations);
/// <inheritdoc />
public byte[] GenerateSalt()
=> GenerateSalt(DefaultSaltLength);
/// <inheritdoc />
public byte[] GenerateSalt(int length)
=> RandomNumberGenerator.GetBytes(length);
{
var salt = new byte[length];
using var rng = RandomNumberGenerator.Create();
rng.GetNonZeroBytes(salt);
return salt;
}
}
}

View File

@@ -4,8 +4,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Jellyfin.Extensions;
using Microsoft.Extensions.Logging;
using SQLitePCL.pretty;
@@ -160,21 +160,22 @@ namespace Emby.Server.Implementations.Data
protected bool TableExists(ManagedConnection connection, string name)
{
return connection.RunInTransaction(
db =>
{
using (var statement = PrepareStatement(db, "select DISTINCT tbl_name from sqlite_master"))
db =>
{
foreach (var row in statement.ExecuteQuery())
using (var statement = PrepareStatement(db, "select DISTINCT tbl_name from sqlite_master"))
{
if (string.Equals(name, row.GetString(0), StringComparison.OrdinalIgnoreCase))
foreach (var row in statement.ExecuteQuery())
{
return true;
if (string.Equals(name, row.GetString(0), StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
}
}
return false;
}, ReadTransactionMode);
return false;
},
ReadTransactionMode);
}
protected List<string> GetColumnNames(IDatabaseConnection connection, string table)
@@ -194,7 +195,7 @@ namespace Emby.Server.Implementations.Data
protected void AddColumn(IDatabaseConnection connection, string table, string columnName, string type, List<string> existingColumnNames)
{
if (existingColumnNames.Contains(columnName, StringComparer.OrdinalIgnoreCase))
if (existingColumnNames.Contains(columnName, StringComparison.OrdinalIgnoreCase))
{
return;
}

View File

@@ -7,7 +7,7 @@ using SQLitePCL.pretty;
namespace Emby.Server.Implementations.Data
{
public class ManagedConnection : IDisposable
public sealed class ManagedConnection : IDisposable
{
private readonly SemaphoreSlim _writeLock;

File diff suppressed because it is too large Load Diff

View File

@@ -50,41 +50,42 @@ namespace Emby.Server.Implementations.Data
var users = userDatasTableExists ? null : userManager.Users;
connection.RunInTransaction(
db =>
{
db.ExecuteAll(string.Join(';', new[]
db =>
{
"create table if not exists UserDatas (key nvarchar not null, userId INT not null, rating float null, played bit not null, playCount int not null, isFavorite bit not null, playbackPositionTicks bigint not null, lastPlayedDate datetime null, AudioStreamIndex INT, SubtitleStreamIndex INT)",
"drop index if exists idx_userdata",
"drop index if exists idx_userdata1",
"drop index if exists idx_userdata2",
"drop index if exists userdataindex1",
"drop index if exists userdataindex",
"drop index if exists userdataindex3",
"drop index if exists userdataindex4",
"create unique index if not exists UserDatasIndex1 on UserDatas (key, userId)",
"create index if not exists UserDatasIndex2 on UserDatas (key, userId, played)",
"create index if not exists UserDatasIndex3 on UserDatas (key, userId, playbackPositionTicks)",
"create index if not exists UserDatasIndex4 on UserDatas (key, userId, isFavorite)"
}));
if (userDataTableExists)
{
var existingColumnNames = GetColumnNames(db, "userdata");
AddColumn(db, "userdata", "InternalUserId", "int", existingColumnNames);
AddColumn(db, "userdata", "AudioStreamIndex", "int", existingColumnNames);
AddColumn(db, "userdata", "SubtitleStreamIndex", "int", existingColumnNames);
if (!userDatasTableExists)
db.ExecuteAll(string.Join(';', new[]
{
ImportUserIds(db, users);
"create table if not exists UserDatas (key nvarchar not null, userId INT not null, rating float null, played bit not null, playCount int not null, isFavorite bit not null, playbackPositionTicks bigint not null, lastPlayedDate datetime null, AudioStreamIndex INT, SubtitleStreamIndex INT)",
db.ExecuteAll("INSERT INTO UserDatas (key, userId, rating, played, playCount, isFavorite, playbackPositionTicks, lastPlayedDate, AudioStreamIndex, SubtitleStreamIndex) SELECT key, InternalUserId, rating, played, playCount, isFavorite, playbackPositionTicks, lastPlayedDate, AudioStreamIndex, SubtitleStreamIndex from userdata where InternalUserId not null");
"drop index if exists idx_userdata",
"drop index if exists idx_userdata1",
"drop index if exists idx_userdata2",
"drop index if exists userdataindex1",
"drop index if exists userdataindex",
"drop index if exists userdataindex3",
"drop index if exists userdataindex4",
"create unique index if not exists UserDatasIndex1 on UserDatas (key, userId)",
"create index if not exists UserDatasIndex2 on UserDatas (key, userId, played)",
"create index if not exists UserDatasIndex3 on UserDatas (key, userId, playbackPositionTicks)",
"create index if not exists UserDatasIndex4 on UserDatas (key, userId, isFavorite)"
}));
if (userDataTableExists)
{
var existingColumnNames = GetColumnNames(db, "userdata");
AddColumn(db, "userdata", "InternalUserId", "int", existingColumnNames);
AddColumn(db, "userdata", "AudioStreamIndex", "int", existingColumnNames);
AddColumn(db, "userdata", "SubtitleStreamIndex", "int", existingColumnNames);
if (!userDatasTableExists)
{
ImportUserIds(db, users);
db.ExecuteAll("INSERT INTO UserDatas (key, userId, rating, played, playCount, isFavorite, playbackPositionTicks, lastPlayedDate, AudioStreamIndex, SubtitleStreamIndex) SELECT key, InternalUserId, rating, played, playCount, isFavorite, playbackPositionTicks, lastPlayedDate, AudioStreamIndex, SubtitleStreamIndex from userdata where InternalUserId not null");
}
}
}
}, TransactionMode);
},
TransactionMode);
}
}
@@ -183,10 +184,11 @@ namespace Emby.Server.Implementations.Data
using (var connection = GetConnection())
{
connection.RunInTransaction(
db =>
{
SaveUserData(db, internalUserId, key, userData);
}, TransactionMode);
db =>
{
SaveUserData(db, internalUserId, key, userData);
},
TransactionMode);
}
}
@@ -252,13 +254,14 @@ namespace Emby.Server.Implementations.Data
using (var connection = GetConnection())
{
connection.RunInTransaction(
db =>
{
foreach (var userItemData in userDataList)
db =>
{
SaveUserData(db, internalUserId, userItemData.Key, userItemData);
}
}, TransactionMode);
foreach (var userItemData in userDataList)
{
SaveUserData(db, internalUserId, userItemData.Key, userItemData);
}
},
TransactionMode);
}
}

View File

@@ -37,7 +37,7 @@ namespace Emby.Server.Implementations.Devices
{
var value = File.ReadAllText(CachePath, Encoding.UTF8);
if (Guid.TryParse(value, out var guid))
if (Guid.TryParse(value, out _))
{
return value;
}

View File

@@ -7,9 +7,9 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Common;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Drawing;
@@ -109,7 +109,7 @@ namespace Emby.Server.Implementations.Dto
}
});
SetItemByNameInfo(item, dto, libraryItems, user);
SetItemByNameInfo(item, dto, libraryItems);
}
}
@@ -153,8 +153,7 @@ namespace Emby.Server.Implementations.Dto
new DtoOptions(false)
{
EnableImages = false
}),
user);
}));
}
return dto;
@@ -294,7 +293,7 @@ namespace Emby.Server.Implementations.Dto
path = path.TrimStart('.');
}
if (!string.IsNullOrEmpty(path) && containers.Contains(path, StringComparer.OrdinalIgnoreCase))
if (!string.IsNullOrEmpty(path) && containers.Contains(path, StringComparison.OrdinalIgnoreCase))
{
fileExtensionContainer = path;
}
@@ -311,13 +310,13 @@ namespace Emby.Server.Implementations.Dto
if (taggedItems != null && options.ContainsField(ItemFields.ItemCounts))
{
SetItemByNameInfo(item, dto, taggedItems, user);
SetItemByNameInfo(item, dto, taggedItems);
}
return dto;
}
private static void SetItemByNameInfo(BaseItem item, BaseItemDto dto, IList<BaseItem> taggedItems, User user = null)
private static void SetItemByNameInfo(BaseItem item, BaseItemDto dto, IList<BaseItem> taggedItems)
{
if (item is MusicArtist)
{
@@ -370,6 +369,12 @@ namespace Emby.Server.Implementations.Dto
if (item is MusicAlbum || item is Season || item is Playlist)
{
dto.ChildCount = dto.RecursiveItemCount;
var folderChildCount = folder.LinkedChildren.Length;
// The default is an empty array, so we can't reliably use the count when it's empty
if (folderChildCount > 0)
{
dto.ChildCount ??= folderChildCount;
}
}
if (options.ContainsField(ItemFields.ChildCount))
@@ -464,7 +469,7 @@ namespace Emby.Server.Implementations.Dto
{
var parentAlbumIds = _libraryManager.GetItemIds(new InternalItemsQuery
{
IncludeItemTypes = new[] { nameof(MusicAlbum) },
IncludeItemTypes = new[] { BaseItemKind.MusicAlbum },
Name = item.Album,
Limit = 1
});
@@ -1398,44 +1403,27 @@ namespace Emby.Server.Implementations.Dto
return null;
}
ImageDimensions size;
var defaultAspectRatio = item.GetDefaultPrimaryImageAspectRatio();
if (defaultAspectRatio > 0)
{
return defaultAspectRatio;
}
if (!imageInfo.IsLocalFile)
{
return null;
return item.GetDefaultPrimaryImageAspectRatio();
}
try
{
size = _imageProcessor.GetImageDimensions(item, imageInfo);
if (size.Width <= 0 || size.Height <= 0)
var size = _imageProcessor.GetImageDimensions(item, imageInfo);
var width = size.Width;
var height = size.Height;
if (width > 0 && height > 0)
{
return null;
return (double)width / height;
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to determine primary image aspect ratio for {0}", imageInfo.Path);
return null;
_logger.LogError(ex, "Failed to determine primary image aspect ratio for {ImagePath}", imageInfo.Path);
}
var width = size.Width;
var height = size.Height;
if (width <= 0 || height <= 0)
{
return null;
}
return (double)width / height;
return item.GetDefaultPrimaryImageAspectRatio();
}
}
}

View File

@@ -29,10 +29,10 @@
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="6.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" 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.0" />
<PackageReference Include="sharpcompress" Version="0.30.1" />
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="3.1.0" />
<PackageReference Include="DotNet.Glob" Version="3.1.3" />
</ItemGroup>
@@ -49,10 +49,14 @@
<NoWarn>AD0001</NoWarn>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
</PropertyGroup>
<!-- Code Analyzers-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>

View File

@@ -13,7 +13,6 @@ using Jellyfin.Networking.Configuration;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Plugins;
using MediaBrowser.Model.Dlna;
using Microsoft.Extensions.Logging;
using Mono.Nat;
@@ -27,7 +26,6 @@ namespace Emby.Server.Implementations.EntryPoints
private readonly IServerApplicationHost _appHost;
private readonly ILogger<ExternalPortForwarding> _logger;
private readonly IServerConfigurationManager _config;
private readonly IDeviceDiscovery _deviceDiscovery;
private readonly ConcurrentDictionary<IPEndPoint, byte> _createdRules = new ConcurrentDictionary<IPEndPoint, byte>();
@@ -42,17 +40,14 @@ namespace Emby.Server.Implementations.EntryPoints
/// <param name="logger">The logger.</param>
/// <param name="appHost">The application host.</param>
/// <param name="config">The configuration manager.</param>
/// <param name="deviceDiscovery">The device discovery.</param>
public ExternalPortForwarding(
ILogger<ExternalPortForwarding> logger,
IServerApplicationHost appHost,
IServerConfigurationManager config,
IDeviceDiscovery deviceDiscovery)
IServerConfigurationManager config)
{
_logger = logger;
_appHost = appHost;
_config = config;
_deviceDiscovery = deviceDiscovery;
}
private string GetConfigIdentifier()

View File

@@ -101,7 +101,7 @@ namespace Emby.Server.Implementations.EntryPoints
}
}
_lastProgressMessageTimes.AddOrUpdate(item.Id, key => DateTime.UtcNow, (key, existing) => DateTime.UtcNow);
_lastProgressMessageTimes.AddOrUpdate(item.Id, _ => DateTime.UtcNow, (_, _) => DateTime.UtcNow);
var dict = new Dictionary<string, string>();
dict["ItemId"] = item.Id.ToString("N", CultureInfo.InvariantCulture);
@@ -144,7 +144,7 @@ namespace Emby.Server.Implementations.EntryPoints
{
OnProviderRefreshProgress(sender, new GenericEventArgs<Tuple<BaseItem, double>>(new Tuple<BaseItem, double>(e.Argument, 100)));
_lastProgressMessageTimes.TryRemove(e.Argument.Id, out DateTime removed);
_lastProgressMessageTimes.TryRemove(e.Argument.Id, out _);
}
private static bool EnableRefreshMessage(BaseItem item)
@@ -423,7 +423,6 @@ namespace Emby.Server.Implementations.EntryPoints
continue;
}
var collectionFolders = _libraryManager.GetCollectionFolders(item, allUserRootChildren);
foreach (var folder in allUserRootChildren)
{
list.Add(folder.Id.ToString("N", CultureInfo.InvariantCulture));
@@ -465,6 +464,7 @@ namespace Emby.Server.Implementations.EntryPoints
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>

View File

@@ -95,7 +95,7 @@ namespace Emby.Server.Implementations.EntryPoints
var changes = _changedItems.ToList();
_changedItems.Clear();
var task = SendNotifications(changes, CancellationToken.None);
SendNotifications(changes, CancellationToken.None).GetAwaiter().GetResult();
if (_updateTimer != null)
{

View File

@@ -2,7 +2,6 @@
using System.Threading.Tasks;
using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Authentication;
using MediaBrowser.Controller.Net;
using Microsoft.AspNetCore.Http;

View File

@@ -47,7 +47,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
{
var session = await GetSession(requestContext).ConfigureAwait(false);
return session == null || session.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(session.UserId);
return session.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(session.UserId);
}
public Task<User?> GetUser(object requestContext)

View File

@@ -42,17 +42,14 @@ namespace Emby.Server.Implementations.HttpServer
/// <param name="logger">The logger.</param>
/// <param name="socket">The socket.</param>
/// <param name="remoteEndPoint">The remote end point.</param>
/// <param name="query">The query.</param>
public WebSocketConnection(
ILogger<WebSocketConnection> logger,
WebSocket socket,
IPAddress? remoteEndPoint,
IQueryCollection query)
IPAddress? remoteEndPoint)
{
_logger = logger;
_socket = socket;
RemoteEndPoint = remoteEndPoint;
QueryString = query;
_jsonOptions = JsonDefaults.Options;
LastActivityDate = DateTime.Now;
@@ -81,12 +78,6 @@ namespace Emby.Server.Implementations.HttpServer
/// <inheritdoc />
public DateTime LastKeepAliveDate { get; set; }
/// <summary>
/// Gets the query string.
/// </summary>
/// <value>The query string.</value>
public IQueryCollection QueryString { get; }
/// <summary>
/// Gets the state.
/// </summary>
@@ -180,7 +171,7 @@ namespace Emby.Server.Implementations.HttpServer
}
WebSocketMessage<object>? stub;
long bytesConsumed = 0;
long bytesConsumed;
try
{
stub = DeserializeWebSocketMessage(buffer, out bytesConsumed);
@@ -236,7 +227,8 @@ namespace Emby.Server.Implementations.HttpServer
{
MessageId = Guid.NewGuid(),
MessageType = SessionMessageType.KeepAlive
}, CancellationToken.None);
},
CancellationToken.None);
}
/// <inheritdoc />

View File

@@ -7,6 +7,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Net.WebSockets;
using System.Threading.Tasks;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Net;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
@@ -50,8 +51,7 @@ namespace Emby.Server.Implementations.HttpServer
using var connection = new WebSocketConnection(
_loggerFactory.CreateLogger<WebSocketConnection>(),
webSocket,
context.Connection.RemoteIpAddress,
context.Request.Query)
context.GetNormalizedRemoteIp())
{
OnReceive = ProcessWebSocketMessageReceived
};
@@ -59,7 +59,7 @@ namespace Emby.Server.Implementations.HttpServer
var tasks = new Task[_webSocketListeners.Length];
for (var i = 0; i < _webSocketListeners.Length; ++i)
{
tasks[i] = _webSocketListeners[i].ProcessWebSocketConnectedAsync(connection);
tasks[i] = _webSocketListeners[i].ProcessWebSocketConnectedAsync(connection, context);
}
await Task.WhenAll(tasks).ConfigureAwait(false);

View File

@@ -1,5 +1,3 @@
#nullable disable
#pragma warning disable CS1591
using System;
@@ -14,7 +12,7 @@ using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.IO
{
public class FileRefresher : IDisposable
public sealed class FileRefresher : IDisposable
{
private readonly ILogger _logger;
private readonly ILibraryManager _libraryManager;
@@ -22,7 +20,7 @@ namespace Emby.Server.Implementations.IO
private readonly List<string> _affectedPaths = new List<string>();
private readonly object _timerLock = new object();
private Timer _timer;
private Timer? _timer;
private bool _disposed;
public FileRefresher(string path, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, ILogger logger)
@@ -36,7 +34,7 @@ namespace Emby.Server.Implementations.IO
AddPath(path);
}
public event EventHandler<EventArgs> Completed;
public event EventHandler<EventArgs>? Completed;
public string Path { get; private set; }
@@ -111,7 +109,7 @@ namespace Emby.Server.Implementations.IO
RestartTimer();
}
private void OnTimerCallback(object state)
private void OnTimerCallback(object? state)
{
List<string> paths;
@@ -127,7 +125,7 @@ namespace Emby.Server.Implementations.IO
try
{
ProcessPathChanges(paths.ToList());
ProcessPathChanges(paths);
}
catch (Exception ex)
{
@@ -137,12 +135,12 @@ namespace Emby.Server.Implementations.IO
private void ProcessPathChanges(List<string> paths)
{
var itemsToRefresh = paths
IEnumerable<BaseItem> itemsToRefresh = paths
.Distinct(StringComparer.OrdinalIgnoreCase)
.Select(GetAffectedBaseItem)
.Where(item => item != null)
.GroupBy(x => x.Id)
.Select(x => x.First());
.GroupBy(x => x!.Id) // Removed null values in the previous .Where()
.Select(x => x.First())!;
foreach (var item in itemsToRefresh)
{
@@ -176,15 +174,15 @@ namespace Emby.Server.Implementations.IO
/// </summary>
/// <param name="path">The path.</param>
/// <returns>BaseItem.</returns>
private BaseItem GetAffectedBaseItem(string path)
private BaseItem? GetAffectedBaseItem(string path)
{
BaseItem item = null;
BaseItem? item = null;
while (item == null && !string.IsNullOrEmpty(path))
{
item = _libraryManager.FindByPath(path, null);
path = System.IO.Path.GetDirectoryName(path);
path = System.IO.Path.GetDirectoryName(path) ?? string.Empty;
}
if (item != null)
@@ -219,8 +217,13 @@ namespace Emby.Server.Implementations.IO
/// <inheritdoc />
public void Dispose()
{
_disposed = true;
if (_disposed)
{
return;
}
DisposeTimer();
_disposed = true;
GC.SuppressFinalize(this);
}
}

View File

@@ -99,7 +99,7 @@ namespace Emby.Server.Implementations.IO
// But if we make this delay too high, we risk missing legitimate changes, such as user adding a new file, or hand-editing metadata
await Task.Delay(45000).ConfigureAwait(false);
_tempIgnoredPaths.TryRemove(path, out var val);
_tempIgnoredPaths.TryRemove(path, out _);
if (refreshPath)
{
@@ -449,12 +449,12 @@ namespace Emby.Server.Implementations.IO
}
var newRefresher = new FileRefresher(path, _configurationManager, _libraryManager, _logger);
newRefresher.Completed += NewRefresher_Completed;
newRefresher.Completed += OnNewRefresherCompleted;
_activeRefreshers.Add(newRefresher);
}
}
private void NewRefresher_Completed(object sender, EventArgs e)
private void OnNewRefresherCompleted(object sender, EventArgs e)
{
var refresher = (FileRefresher)sender;
DisposeRefresher(refresher);
@@ -481,6 +481,7 @@ namespace Emby.Server.Implementations.IO
{
lock (_activeRefreshers)
{
refresher.Completed -= OnNewRefresherCompleted;
refresher.Dispose();
_activeRefreshers.Remove(refresher);
}
@@ -492,6 +493,7 @@ namespace Emby.Server.Implementations.IO
{
foreach (var refresher in _activeRefreshers.ToList())
{
refresher.Completed -= OnNewRefresherCompleted;
refresher.Dispose();
}

View File

@@ -544,16 +544,6 @@ namespace Emby.Server.Implementations.IO
/// <inheritdoc />
public virtual bool AreEqual(string path1, string path2)
{
if (path1 == null && path2 == null)
{
return true;
}
if (path1 == null || path2 == null)
{
return false;
}
return string.Equals(
NormalizePath(path1),
NormalizePath(path2),

View File

@@ -17,11 +17,11 @@ namespace Emby.Server.Implementations.IO
try
{
int read;
while ((read = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0)
while ((read = await source.ReadAsync(buffer, cancellationToken).ConfigureAwait(false)) != 0)
{
cancellationToken.ThrowIfCancellationRequested();
await destination.WriteAsync(buffer, 0, read, cancellationToken).ConfigureAwait(false);
await destination.WriteAsync(buffer.AsMemory(0, read), cancellationToken).ConfigureAwait(false);
if (onStarted != null)
{
@@ -44,11 +44,11 @@ namespace Emby.Server.Implementations.IO
if (emptyReadLimit <= 0)
{
int read;
while ((read = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0)
while ((read = await source.ReadAsync(buffer, cancellationToken).ConfigureAwait(false)) != 0)
{
cancellationToken.ThrowIfCancellationRequested();
await destination.WriteAsync(buffer, 0, read, cancellationToken).ConfigureAwait(false);
await destination.WriteAsync(buffer.AsMemory(0, read), cancellationToken).ConfigureAwait(false);
}
return;
@@ -60,7 +60,7 @@ namespace Emby.Server.Implementations.IO
{
cancellationToken.ThrowIfCancellationRequested();
var bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
var bytesRead = await source.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
if (bytesRead == 0)
{
@@ -71,7 +71,7 @@ namespace Emby.Server.Implementations.IO
{
eofCount = 0;
await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false);
await destination.WriteAsync(buffer.AsMemory(0, bytesRead), cancellationToken).ConfigureAwait(false);
}
}
}
@@ -88,13 +88,13 @@ namespace Emby.Server.Implementations.IO
{
int bytesRead;
while ((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0)
while ((bytesRead = await source.ReadAsync(buffer, cancellationToken).ConfigureAwait(false)) != 0)
{
var bytesToWrite = Math.Min(bytesRead, copyLength);
if (bytesToWrite > 0)
{
await destination.WriteAsync(buffer, 0, Convert.ToInt32(bytesToWrite), cancellationToken).ConfigureAwait(false);
await destination.WriteAsync(buffer.AsMemory(0, Convert.ToInt32(bytesToWrite)), cancellationToken).ConfigureAwait(false);
}
copyLength -= bytesToWrite;
@@ -137,9 +137,9 @@ namespace Emby.Server.Implementations.IO
int bytesRead;
int totalBytesRead = 0;
while ((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0)
while ((bytesRead = await source.ReadAsync(buffer, cancellationToken).ConfigureAwait(false)) != 0)
{
await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false);
await destination.WriteAsync(buffer.AsMemory(0, bytesRead), cancellationToken).ConfigureAwait(false);
totalBytesRead += bytesRead;
}

View File

@@ -28,35 +28,35 @@ namespace Emby.Server.Implementations.Images
var view = (CollectionFolder)item;
var viewType = view.CollectionType;
string[] includeItemTypes;
BaseItemKind[] includeItemTypes;
if (string.Equals(viewType, CollectionType.Movies, StringComparison.Ordinal))
{
includeItemTypes = new string[] { "Movie" };
includeItemTypes = new[] { BaseItemKind.Movie };
}
else if (string.Equals(viewType, CollectionType.TvShows, StringComparison.Ordinal))
{
includeItemTypes = new string[] { "Series" };
includeItemTypes = new[] { BaseItemKind.Series };
}
else if (string.Equals(viewType, CollectionType.Music, StringComparison.Ordinal))
{
includeItemTypes = new string[] { "MusicAlbum" };
includeItemTypes = new[] { BaseItemKind.MusicAlbum };
}
else if (string.Equals(viewType, CollectionType.Books, StringComparison.Ordinal))
{
includeItemTypes = new string[] { "Book", "AudioBook" };
includeItemTypes = new[] { BaseItemKind.Book, BaseItemKind.AudioBook };
}
else if (string.Equals(viewType, CollectionType.BoxSets, StringComparison.Ordinal))
{
includeItemTypes = new string[] { "BoxSet" };
includeItemTypes = new[] { BaseItemKind.BoxSet };
}
else if (string.Equals(viewType, CollectionType.HomeVideos, StringComparison.Ordinal) || string.Equals(viewType, CollectionType.Photos, StringComparison.Ordinal))
{
includeItemTypes = new string[] { "Video", "Photo" };
includeItemTypes = new[] { BaseItemKind.Video, BaseItemKind.Photo };
}
else
{
includeItemTypes = new string[] { "Video", "Audio", "Photo", "Movie", "Series" };
includeItemTypes = new[] { BaseItemKind.Video, BaseItemKind.Audio, BaseItemKind.Photo, BaseItemKind.Movie, BaseItemKind.Series };
}
var recursive = !string.Equals(CollectionType.Playlists, viewType, StringComparison.OrdinalIgnoreCase);
@@ -68,9 +68,9 @@ namespace Emby.Server.Implementations.Images
DtoOptions = new DtoOptions(false),
ImageTypes = new ImageType[] { ImageType.Primary },
Limit = 8,
OrderBy = new ValueTuple<string, SortOrder>[]
OrderBy = new[]
{
new ValueTuple<string, SortOrder>(ItemSortBy.Random, SortOrder.Ascending)
(ItemSortBy.Random, SortOrder.Ascending)
},
IncludeItemTypes = includeItemTypes
});

View File

@@ -6,6 +6,8 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto;
@@ -34,14 +36,14 @@ namespace Emby.Server.Implementations.Images
var view = (UserView)item;
var isUsingCollectionStrip = IsUsingCollectionStrip(view);
var recursive = isUsingCollectionStrip && !new[] { CollectionType.BoxSets, CollectionType.Playlists }.Contains(view.ViewType ?? string.Empty, StringComparer.OrdinalIgnoreCase);
var recursive = isUsingCollectionStrip && !new[] { CollectionType.BoxSets, CollectionType.Playlists }.Contains(view.ViewType ?? string.Empty, StringComparison.OrdinalIgnoreCase);
var result = view.GetItemList(new InternalItemsQuery
{
User = view.UserId.HasValue ? _userManager.GetUserById(view.UserId.Value) : null,
CollapseBoxSetItems = false,
Recursive = recursive,
ExcludeItemTypes = new[] { "UserView", "CollectionFolder", "Person" },
ExcludeItemTypes = new[] { BaseItemKind.UserView, BaseItemKind.CollectionFolder, BaseItemKind.Person },
DtoOptions = new DtoOptions(false)
});

View File

@@ -8,8 +8,6 @@ using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
@@ -43,7 +41,7 @@ namespace Emby.Server.Implementations.Images
return _libraryManager.GetItemList(new InternalItemsQuery
{
Genres = new[] { item.Name },
IncludeItemTypes = new[] { nameof(Series), nameof(Movie) },
IncludeItemTypes = new[] { BaseItemKind.Series, BaseItemKind.Movie },
OrderBy = new[] { (ItemSortBy.Random, SortOrder.Ascending) },
Limit = 4,
Recursive = true,

View File

@@ -44,9 +44,9 @@ namespace Emby.Server.Implementations.Images
Genres = new[] { item.Name },
IncludeItemTypes = new[]
{
nameof(MusicAlbum),
nameof(MusicVideo),
nameof(Audio)
BaseItemKind.MusicAlbum,
BaseItemKind.MusicVideo,
BaseItemKind.Audio
},
OrderBy = new[] { (ItemSortBy.Random, SortOrder.Ascending) },
Limit = 4,

View File

@@ -1,8 +1,9 @@
using System;
using System.IO;
using Emby.Naming.Audio;
using Emby.Naming.Common;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Resolvers;
using MediaBrowser.Model.IO;
@@ -13,17 +14,17 @@ namespace Emby.Server.Implementations.Library
/// </summary>
public class CoreResolutionIgnoreRule : IResolverIgnoreRule
{
private readonly ILibraryManager _libraryManager;
private readonly NamingOptions _namingOptions;
private readonly IServerApplicationPaths _serverApplicationPaths;
/// <summary>
/// Initializes a new instance of the <see cref="CoreResolutionIgnoreRule"/> class.
/// </summary>
/// <param name="libraryManager">The library manager.</param>
/// <param name="namingOptions">The naming options.</param>
/// <param name="serverApplicationPaths">The server application paths.</param>
public CoreResolutionIgnoreRule(ILibraryManager libraryManager, IServerApplicationPaths serverApplicationPaths)
public CoreResolutionIgnoreRule(NamingOptions namingOptions, IServerApplicationPaths serverApplicationPaths)
{
_libraryManager = libraryManager;
_namingOptions = namingOptions;
_serverApplicationPaths = serverApplicationPaths;
}
@@ -53,20 +54,10 @@ namespace Emby.Server.Implementations.Library
{
if (parent != null)
{
// Ignore trailer folders but allow it at the collection level
if (string.Equals(filename, BaseItem.TrailersFolderName, StringComparison.OrdinalIgnoreCase)
&& !(parent is AggregateFolder)
&& !(parent is UserRootFolder))
{
return true;
}
if (string.Equals(filename, BaseItem.ThemeVideosFolderName, StringComparison.OrdinalIgnoreCase))
{
return true;
}
if (string.Equals(filename, BaseItem.ThemeSongsFolderName, StringComparison.OrdinalIgnoreCase))
// Ignore extras folders but allow it at the collection level
if (_namingOptions.AllExtrasTypesFolderNames.ContainsKey(filename)
&& parent is not AggregateFolder
&& parent is not UserRootFolder)
{
return true;
}
@@ -78,7 +69,7 @@ namespace Emby.Server.Implementations.Library
{
// Don't resolve these into audio files
if (Path.GetFileNameWithoutExtension(filename.AsSpan()).Equals(BaseItem.ThemeSongFileName, StringComparison.Ordinal)
&& _libraryManager.IsAudioFile(filename))
&& AudioFileParser.IsAudioFile(filename, _namingOptions))
{
return true;
}

View File

@@ -11,14 +11,12 @@ using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Emby.Naming.Audio;
using Emby.Naming.Common;
using Emby.Naming.TV;
using Emby.Naming.Video;
using Emby.Server.Implementations.Library.Resolvers;
using Emby.Server.Implementations.Library.Validators;
using Emby.Server.Implementations.Playlists;
using Emby.Server.Implementations.ScheduledTasks;
using Emby.Server.Implementations.ScheduledTasks.Tasks;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
@@ -79,6 +77,8 @@ namespace Emby.Server.Implementations.Library
private readonly IFileSystem _fileSystem;
private readonly IItemRepository _itemRepository;
private readonly IImageProcessor _imageProcessor;
private readonly NamingOptions _namingOptions;
private readonly ExtraResolver _extraResolver;
/// <summary>
/// The _root folder sync lock.
@@ -88,9 +88,6 @@ namespace Emby.Server.Implementations.Library
private readonly TimeSpan _viewRefreshInterval = TimeSpan.FromHours(24);
private NamingOptions _namingOptions;
private string[] _videoFileExtensions;
/// <summary>
/// The _root folder.
/// </summary>
@@ -116,6 +113,7 @@ namespace Emby.Server.Implementations.Library
/// <param name="itemRepository">The item repository.</param>
/// <param name="imageProcessor">The image processor.</param>
/// <param name="memoryCache">The memory cache.</param>
/// <param name="namingOptions">The naming options.</param>
public LibraryManager(
IServerApplicationHost appHost,
ILogger<LibraryManager> logger,
@@ -130,7 +128,8 @@ namespace Emby.Server.Implementations.Library
IMediaEncoder mediaEncoder,
IItemRepository itemRepository,
IImageProcessor imageProcessor,
IMemoryCache memoryCache)
IMemoryCache memoryCache,
NamingOptions namingOptions)
{
_appHost = appHost;
_logger = logger;
@@ -146,6 +145,9 @@ namespace Emby.Server.Implementations.Library
_itemRepository = itemRepository;
_imageProcessor = imageProcessor;
_memoryCache = memoryCache;
_namingOptions = namingOptions;
_extraResolver = new ExtraResolver(namingOptions);
_configurationManager.ConfigurationUpdated += ConfigurationUpdated;
@@ -532,8 +534,8 @@ namespace Emby.Server.Implementations.Library
return key.GetMD5();
}
public BaseItem ResolvePath(FileSystemMetadata fileInfo, Folder parent = null)
=> ResolvePath(fileInfo, new DirectoryService(_fileSystem), null, parent);
public BaseItem ResolvePath(FileSystemMetadata fileInfo, Folder parent = null, IDirectoryService directoryService = null)
=> ResolvePath(fileInfo, directoryService ?? new DirectoryService(_fileSystem), null, parent);
private BaseItem ResolvePath(
FileSystemMetadata fileInfo,
@@ -653,7 +655,7 @@ namespace Emby.Server.Implementations.Library
return !args.ContainsFileSystemEntryByName(".ignore");
}
public IEnumerable<BaseItem> ResolvePaths(IEnumerable<FileSystemMetadata> files, IDirectoryService directoryService, Folder parent, LibraryOptions libraryOptions, string collectionType)
public IEnumerable<BaseItem> ResolvePaths(IEnumerable<FileSystemMetadata> files, IDirectoryService directoryService, Folder parent, LibraryOptions libraryOptions, string collectionType = null)
{
return ResolvePaths(files, directoryService, parent, libraryOptions, collectionType, EntityResolvers);
}
@@ -676,7 +678,7 @@ namespace Emby.Server.Implementations.Library
{
var result = resolver.ResolveMultiple(parent, fileList, collectionType, directoryService);
if (result != null && result.Items.Count > 0)
if (result?.Items.Count > 0)
{
var items = new List<BaseItem>();
items.AddRange(result.Items);
@@ -964,7 +966,7 @@ namespace Emby.Server.Implementations.Library
{
var existing = GetItemList(new InternalItemsQuery
{
IncludeItemTypes = new[] { nameof(MusicArtist) },
IncludeItemTypes = new[] { BaseItemKind.MusicArtist },
Name = name,
DtoOptions = options
}).Cast<MusicArtist>()
@@ -1374,7 +1376,7 @@ namespace Emby.Server.Implementations.Library
return _itemRepository.GetItemIdsList(query);
}
public QueryResult<(BaseItem, ItemCounts)> GetStudios(InternalItemsQuery query)
public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetStudios(InternalItemsQuery query)
{
if (query.User != null)
{
@@ -1385,7 +1387,7 @@ namespace Emby.Server.Implementations.Library
return _itemRepository.GetStudios(query);
}
public QueryResult<(BaseItem, ItemCounts)> GetGenres(InternalItemsQuery query)
public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetGenres(InternalItemsQuery query)
{
if (query.User != null)
{
@@ -1396,7 +1398,7 @@ namespace Emby.Server.Implementations.Library
return _itemRepository.GetGenres(query);
}
public QueryResult<(BaseItem, ItemCounts)> GetMusicGenres(InternalItemsQuery query)
public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetMusicGenres(InternalItemsQuery query)
{
if (query.User != null)
{
@@ -1407,7 +1409,7 @@ namespace Emby.Server.Implementations.Library
return _itemRepository.GetMusicGenres(query);
}
public QueryResult<(BaseItem, ItemCounts)> GetAllArtists(InternalItemsQuery query)
public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetAllArtists(InternalItemsQuery query)
{
if (query.User != null)
{
@@ -1418,7 +1420,7 @@ namespace Emby.Server.Implementations.Library
return _itemRepository.GetAllArtists(query);
}
public QueryResult<(BaseItem, ItemCounts)> GetArtists(InternalItemsQuery query)
public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetArtists(InternalItemsQuery query)
{
if (query.User != null)
{
@@ -1459,7 +1461,7 @@ namespace Emby.Server.Implementations.Library
}
}
public QueryResult<(BaseItem, ItemCounts)> GetAlbumArtists(InternalItemsQuery query)
public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetAlbumArtists(InternalItemsQuery query)
{
if (query.User != null)
{
@@ -1758,7 +1760,7 @@ namespace Emby.Server.Implementations.Library
return orderedItems ?? items;
}
public IEnumerable<BaseItem> Sort(IEnumerable<BaseItem> items, User user, IEnumerable<ValueTuple<string, SortOrder>> orderBy)
public IEnumerable<BaseItem> Sort(IEnumerable<BaseItem> items, User user, IEnumerable<(string OrderBy, SortOrder SortOrder)> orderBy)
{
var isFirst = true;
@@ -2500,16 +2502,6 @@ namespace Emby.Server.Implementations.Library
return RootFolder;
}
/// <inheritdoc />
public bool IsVideoFile(string path)
{
return VideoResolver.IsVideoFile(path, GetNamingOptions());
}
/// <inheritdoc />
public bool IsAudioFile(string path)
=> AudioFileParser.IsAudioFile(path, GetNamingOptions());
/// <inheritdoc />
public int? GetSeasonNumberFromPath(string path)
=> SeasonPathParser.Parse(path, true, true).SeasonNumber;
@@ -2525,7 +2517,7 @@ namespace Emby.Server.Implementations.Library
isAbsoluteNaming = null;
}
var resolver = new EpisodeResolver(GetNamingOptions());
var resolver = new EpisodeResolver(_namingOptions);
var isFolder = episode.VideoType == VideoType.BluRay || episode.VideoType == VideoType.Dvd;
@@ -2682,113 +2674,77 @@ namespace Emby.Server.Implementations.Library
return changed;
}
/// <inheritdoc />
public NamingOptions GetNamingOptions()
{
if (_namingOptions == null)
{
_namingOptions = new NamingOptions();
_videoFileExtensions = _namingOptions.VideoFileExtensions;
}
return _namingOptions;
}
public ItemLookupInfo ParseName(string name)
{
var namingOptions = GetNamingOptions();
var namingOptions = _namingOptions;
var result = VideoResolver.CleanDateTime(name, namingOptions);
return new ItemLookupInfo
{
Name = VideoResolver.TryCleanString(result.Name, namingOptions, out var newName) ? newName.ToString() : result.Name,
Name = VideoResolver.TryCleanString(result.Name, namingOptions, out var newName) ? newName : result.Name,
Year = result.Year
};
}
public IEnumerable<Video> FindTrailers(BaseItem owner, List<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService)
public IEnumerable<BaseItem> FindExtras(BaseItem owner, List<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService)
{
var namingOptions = GetNamingOptions();
var files = owner.IsInMixedFolder ? new List<FileSystemMetadata>() : fileSystemChildren.Where(i => i.IsDirectory)
.Where(i => string.Equals(i.Name, BaseItem.TrailersFolderName, StringComparison.OrdinalIgnoreCase))
.SelectMany(i => _fileSystem.GetFiles(i.FullName, _videoFileExtensions, false, false))
.ToList();
var videos = VideoListResolver.Resolve(fileSystemChildren, namingOptions);
var currentVideo = videos.FirstOrDefault(i => string.Equals(owner.Path, i.Files[0].Path, StringComparison.OrdinalIgnoreCase));
if (currentVideo != null)
var ownerVideoInfo = VideoResolver.Resolve(owner.Path, owner.IsFolder, _namingOptions);
if (ownerVideoInfo == null)
{
files.AddRange(currentVideo.Extras.Where(i => i.ExtraType == ExtraType.Trailer).Select(i => _fileSystem.GetFileInfo(i.Path)));
yield break;
}
var resolvers = new IItemResolver[]
var count = fileSystemChildren.Count;
for (var i = 0; i < count; i++)
{
new GenericVideoResolver<Trailer>(this)
};
return ResolvePaths(files, directoryService, null, new LibraryOptions(), null, resolvers)
.OfType<Trailer>()
.Select(video =>
var current = fileSystemChildren[i];
if (current.IsDirectory && _namingOptions.AllExtrasTypesFolderNames.ContainsKey(current.Name))
{
// Try to retrieve it from the db. If we don't find it, use the resolved version
if (GetItemById(video.Id) is Trailer dbItem)
var filesInSubFolder = _fileSystem.GetFiles(current.FullName, null, false, false);
foreach (var file in filesInSubFolder)
{
video = dbItem;
if (!_extraResolver.TryGetExtraTypeForOwner(file.FullName, ownerVideoInfo, out var extraType))
{
continue;
}
var extra = GetExtra(file, extraType.Value);
if (extra != null)
{
yield return extra;
}
}
video.ParentId = Guid.Empty;
video.OwnerId = owner.Id;
video.ExtraType = ExtraType.Trailer;
video.TrailerTypes = new[] { TrailerType.LocalTrailer };
return video;
// Sort them so that the list can be easily compared for changes
}).OrderBy(i => i.Path);
}
public IEnumerable<Video> FindExtras(BaseItem owner, List<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService)
{
var namingOptions = GetNamingOptions();
var files = owner.IsInMixedFolder ? new List<FileSystemMetadata>() : fileSystemChildren.Where(i => i.IsDirectory)
.Where(i => BaseItem.AllExtrasTypesFolderNames.ContainsKey(i.Name ?? string.Empty))
.SelectMany(i => _fileSystem.GetFiles(i.FullName, _videoFileExtensions, false, false))
.ToList();
var videos = VideoListResolver.Resolve(fileSystemChildren, namingOptions);
var currentVideo = videos.FirstOrDefault(i => string.Equals(owner.Path, i.Files[0].Path, StringComparison.OrdinalIgnoreCase));
if (currentVideo != null)
{
files.AddRange(currentVideo.Extras.Where(i => i.ExtraType != ExtraType.Trailer).Select(i => _fileSystem.GetFileInfo(i.Path)));
}
else if (!current.IsDirectory && _extraResolver.TryGetExtraTypeForOwner(current.FullName, ownerVideoInfo, out var extraType))
{
var extra = GetExtra(current, extraType.Value);
if (extra != null)
{
yield return extra;
}
}
}
return ResolvePaths(files, directoryService, null, new LibraryOptions(), null)
.OfType<Video>()
.Select(video =>
BaseItem GetExtra(FileSystemMetadata file, ExtraType extraType)
{
var extra = ResolvePath(_fileSystem.GetFileInfo(file.FullName), directoryService, _extraResolver.GetResolversForExtraType(extraType));
if (extra is not Video && extra is not Audio)
{
// Try to retrieve it from the db. If we don't find it, use the resolved version
var dbItem = GetItemById(video.Id) as Video;
return null;
}
if (dbItem != null)
{
video = dbItem;
}
// Try to retrieve it from the db. If we don't find it, use the resolved version
var itemById = GetItemById(extra.Id);
if (itemById != null)
{
extra = itemById;
}
video.ParentId = Guid.Empty;
video.OwnerId = owner.Id;
SetExtraTypeFromFilename(video);
return video;
// Sort them so that the list can be easily compared for changes
}).OrderBy(i => i.Path);
extra.ExtraType = extraType;
extra.ParentId = Guid.Empty;
extra.OwnerId = owner.Id;
return extra;
}
}
public string GetPathAfterNetworkSubstitution(string path, BaseItem ownerItem)
@@ -2838,15 +2794,6 @@ namespace Emby.Server.Implementations.Library
return path;
}
private void SetExtraTypeFromFilename(Video item)
{
var resolver = new ExtraResolver(GetNamingOptions());
var result = resolver.GetExtraInfo(item.Path);
item.ExtraType = result.ExtraType;
}
public List<PersonInfo> GetPeople(InternalPeopleQuery query)
{
return _itemRepository.GetPeople(query);
@@ -2953,11 +2900,12 @@ namespace Emby.Server.Implementations.Library
var rootFolderPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath;
var existingNameCount = 1; // first numbered name will be 2
var virtualFolderPath = Path.Combine(rootFolderPath, name);
while (Directory.Exists(virtualFolderPath))
{
name += "1";
virtualFolderPath = Path.Combine(rootFolderPath, name);
existingNameCount++;
virtualFolderPath = Path.Combine(rootFolderPath, name + " " + existingNameCount);
}
var mediaPathInfos = options.PathInfos;

View File

@@ -66,11 +66,8 @@ namespace Emby.Server.Implementations.Library
{
var delayMs = mediaSource.AnalyzeDurationMs ?? 0;
delayMs = Math.Max(3000, delayMs);
if (delayMs > 0)
{
_logger.LogInformation("Waiting {0}ms before probing the live stream", delayMs);
await Task.Delay(delayMs, cancellationToken).ConfigureAwait(false);
}
_logger.LogInformation("Waiting {0}ms before probing the live stream", delayMs);
await Task.Delay(delayMs, cancellationToken).ConfigureAwait(false);
}
mediaSource.AnalyzeDurationMs = 3000;

View File

@@ -464,12 +464,11 @@ namespace Emby.Server.Implementations.Library
try
{
var tuple = GetProvider(request.OpenToken);
var provider = tuple.Item1;
var (provider, keyId) = GetProvider(request.OpenToken);
var currentLiveStreams = _openStreams.Values.ToList();
liveStream = await provider.OpenMediaSource(tuple.Item2, currentLiveStreams, cancellationToken).ConfigureAwait(false);
liveStream = await provider.OpenMediaSource(keyId, currentLiveStreams, cancellationToken).ConfigureAwait(false);
mediaSource = liveStream.MediaSource;
@@ -829,7 +828,7 @@ namespace Emby.Server.Implementations.Library
}
}
private (IMediaSourceProvider, string) GetProvider(string key)
private (IMediaSourceProvider MediaSourceProvider, string KeyId) GetProvider(string key)
{
if (string.IsNullOrEmpty(key))
{

View File

@@ -6,6 +6,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Model.Entities;
namespace Emby.Server.Implementations.Library
@@ -64,18 +65,18 @@ namespace Emby.Server.Implementations.Library
stream = sortedStreams.FirstOrDefault(s => s.IsExternal || s.IsForced || s.IsDefault);
// if the audio language is not understood by the user, load their preferred subs, if there are any
if (stream == null && !preferredLanguages.Contains(audioTrackLanguage, StringComparer.OrdinalIgnoreCase))
if (stream == null && !preferredLanguages.Contains(audioTrackLanguage, StringComparison.OrdinalIgnoreCase))
{
stream = sortedStreams.FirstOrDefault(s => !s.IsForced && preferredLanguages.Contains(s.Language, StringComparer.OrdinalIgnoreCase));
stream = sortedStreams.FirstOrDefault(s => !s.IsForced && preferredLanguages.Contains(s.Language, StringComparison.OrdinalIgnoreCase));
}
}
else if (mode == SubtitlePlaybackMode.Smart)
{
// if the audio language is not understood by the user, load their preferred subs, if there are any
if (!preferredLanguages.Contains(audioTrackLanguage, StringComparer.OrdinalIgnoreCase))
if (!preferredLanguages.Contains(audioTrackLanguage, StringComparison.OrdinalIgnoreCase))
{
stream = streams.FirstOrDefault(s => !s.IsForced && preferredLanguages.Contains(s.Language, StringComparer.OrdinalIgnoreCase)) ??
streams.FirstOrDefault(s => preferredLanguages.Contains(s.Language, StringComparer.OrdinalIgnoreCase));
stream = streams.FirstOrDefault(s => !s.IsForced && preferredLanguages.Contains(s.Language, StringComparison.OrdinalIgnoreCase)) ??
streams.FirstOrDefault(s => preferredLanguages.Contains(s.Language, StringComparison.OrdinalIgnoreCase));
}
}
else if (mode == SubtitlePlaybackMode.Always)
@@ -136,9 +137,9 @@ namespace Emby.Server.Implementations.Library
else if (mode == SubtitlePlaybackMode.Smart)
{
// Prefer smart logic over embedded metadata
if (!preferredLanguages.Contains(audioTrackLanguage, StringComparer.OrdinalIgnoreCase))
if (!preferredLanguages.Contains(audioTrackLanguage, StringComparison.OrdinalIgnoreCase))
{
filteredStreams = streams.Where(s => !s.IsForced && preferredLanguages.Contains(s.Language, StringComparer.OrdinalIgnoreCase))
filteredStreams = streams.Where(s => !s.IsForced && preferredLanguages.Contains(s.Language, StringComparison.OrdinalIgnoreCase))
.ToList();
}
}

View File

@@ -52,7 +52,7 @@ namespace Emby.Server.Implementations.Library
var genres = item
.GetRecursiveChildren(user, new InternalItemsQuery(user)
{
IncludeItemTypes = new[] { nameof(Audio) },
IncludeItemTypes = new[] { BaseItemKind.Audio },
DtoOptions = dtoOptions
})
.Cast<Audio>()
@@ -89,7 +89,7 @@ namespace Emby.Server.Implementations.Library
{
return _libraryManager.GetItemList(new InternalItemsQuery(user)
{
IncludeItemTypes = new[] { nameof(Audio) },
IncludeItemTypes = new[] { BaseItemKind.Audio },
GenreIds = genreIds.ToArray(),
@@ -103,7 +103,7 @@ namespace Emby.Server.Implementations.Library
public List<BaseItem> GetInstantMixFromItem(BaseItem item, User user, DtoOptions dtoOptions)
{
if (item is MusicGenre genre)
if (item is MusicGenre)
{
return GetInstantMixFromGenreIds(new[] { item.Id }, user, dtoOptions);
}

View File

@@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.Library
/// <param name="attribute">The attrib.</param>
/// <returns>System.String.</returns>
/// <exception cref="ArgumentException"><paramref name="str" /> or <paramref name="attribute" /> is empty.</exception>
public static string? GetAttributeValue(this string str, string attribute)
public static string? GetAttributeValue(this ReadOnlySpan<char> str, ReadOnlySpan<char> attribute)
{
if (str.Length == 0)
{
@@ -28,17 +28,31 @@ namespace Emby.Server.Implementations.Library
throw new ArgumentException("String can't be empty.", nameof(attribute));
}
string srch = "[" + attribute + "=";
int start = str.IndexOf(srch, StringComparison.OrdinalIgnoreCase);
if (start != -1)
var attributeIndex = str.IndexOf(attribute, StringComparison.OrdinalIgnoreCase);
// Must be at least 3 characters after the attribute =, ], any character.
var maxIndex = str.Length - attribute.Length - 3;
while (attributeIndex > -1 && attributeIndex < maxIndex)
{
start += srch.Length;
int end = str.IndexOf(']', start);
return str.Substring(start, end - start);
var attributeEnd = attributeIndex + attribute.Length;
if (attributeIndex > 0
&& str[attributeIndex - 1] == '['
&& (str[attributeEnd] == '=' || str[attributeEnd] == '-'))
{
var closingIndex = str[attributeEnd..].IndexOf(']');
// Must be at least 1 character before the closing bracket.
if (closingIndex > 1)
{
return str[(attributeEnd + 1)..(attributeEnd + closingIndex)].Trim().ToString();
}
}
str = str[attributeEnd..];
attributeIndex = str.IndexOf(attribute, StringComparison.OrdinalIgnoreCase);
}
// for imdbid we also accept pattern matching
if (string.Equals(attribute, "imdbid", StringComparison.OrdinalIgnoreCase))
if (attribute.Equals("imdbid", StringComparison.OrdinalIgnoreCase))
{
var match = ProviderIdParsers.TryFindImdbId(str, out var imdbId);
return match ? imdbId.ToString() : null;

View File

@@ -6,7 +6,10 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Emby.Naming.Audio;
using Emby.Naming.AudioBook;
using Emby.Naming.Common;
using Emby.Naming.Video;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
@@ -21,11 +24,11 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
/// </summary>
public class AudioResolver : ItemResolver<MediaBrowser.Controller.Entities.Audio.Audio>, IMultiItemResolver
{
private readonly ILibraryManager _libraryManager;
private readonly NamingOptions _namingOptions;
public AudioResolver(ILibraryManager libraryManager)
public AudioResolver(NamingOptions namingOptions)
{
_libraryManager = libraryManager;
_namingOptions = namingOptions;
}
/// <summary>
@@ -40,7 +43,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
string collectionType,
IDirectoryService directoryService)
{
var result = ResolveMultipleInternal(parent, files, collectionType, directoryService);
var result = ResolveMultipleInternal(parent, files, collectionType);
if (result != null)
{
@@ -56,12 +59,11 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
private MultiItemResolverResult ResolveMultipleInternal(
Folder parent,
List<FileSystemMetadata> files,
string collectionType,
IDirectoryService directoryService)
string collectionType)
{
if (string.Equals(collectionType, CollectionType.Books, StringComparison.OrdinalIgnoreCase))
{
return ResolveMultipleAudio<AudioBook>(parent, files, directoryService, false, collectionType, true);
return ResolveMultipleAudio(parent, files, true);
}
return null;
@@ -87,14 +89,10 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
return null;
}
var files = args.FileSystemChildren
.Where(i => !_libraryManager.IgnoreFile(i, args.Parent))
.ToList();
return FindAudio<AudioBook>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, false);
return FindAudioBook(args, false);
}
if (_libraryManager.IsAudioFile(args.Path))
if (AudioFileParser.IsAudioFile(args.Path, _namingOptions))
{
var extension = Path.GetExtension(args.Path);
@@ -107,7 +105,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
var isMixedCollectionType = string.IsNullOrEmpty(collectionType);
// For conflicting extensions, give priority to videos
if (isMixedCollectionType && _libraryManager.IsVideoFile(args.Path))
if (isMixedCollectionType && VideoResolver.IsVideoFile(args.Path, _namingOptions))
{
return null;
}
@@ -141,29 +139,23 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
return null;
}
private T FindAudio<T>(ItemResolveArgs args, string path, Folder parent, List<FileSystemMetadata> fileSystemEntries, IDirectoryService directoryService, string collectionType, bool parseName)
where T : MediaBrowser.Controller.Entities.Audio.Audio, new()
private AudioBook FindAudioBook(ItemResolveArgs args, bool parseName)
{
// TODO: Allow GetMultiDiscMovie in here
const bool supportsMultiVersion = false;
var result = ResolveMultipleAudio(args.Parent, args.GetActualFileSystemChildren(), parseName);
var result = ResolveMultipleAudio<T>(parent, fileSystemEntries, directoryService, supportsMultiVersion, collectionType, parseName) ??
new MultiItemResolverResult();
if (result.Items.Count == 1)
if (result == null || result.Items.Count != 1 || result.Items[0] is not AudioBook item)
{
// If we were supporting this we'd be checking filesFromOtherItems
var item = (T)result.Items[0];
item.IsInMixedFolder = false;
item.Name = Path.GetFileName(item.ContainingFolderPath);
return item;
return null;
}
return null;
// If we were supporting this we'd be checking filesFromOtherItems
item.IsInMixedFolder = false;
item.Name = Path.GetFileName(item.ContainingFolderPath);
return item;
}
private MultiItemResolverResult ResolveMultipleAudio<T>(Folder parent, IEnumerable<FileSystemMetadata> fileSystemEntries, IDirectoryService directoryService, bool suppportMultiEditions, string collectionType, bool parseName)
where T : MediaBrowser.Controller.Entities.Audio.Audio, new()
private MultiItemResolverResult ResolveMultipleAudio(Folder parent, IEnumerable<FileSystemMetadata> fileSystemEntries, bool parseName)
{
var files = new List<FileSystemMetadata>();
var items = new List<BaseItem>();
@@ -176,15 +168,13 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
{
leftOver.Add(child);
}
else if (!IsIgnored(child.Name))
else
{
files.Add(child);
}
}
var namingOptions = ((LibraryManager)_libraryManager).GetNamingOptions();
var resolver = new AudioBookListResolver(namingOptions);
var resolver = new AudioBookListResolver(_namingOptions);
var resolverResult = resolver.Resolve(files).ToList();
var result = new MultiItemResolverResult
@@ -210,7 +200,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
var firstMedia = resolvedItem.Files[0];
var libraryItem = new T
var libraryItem = new AudioBook
{
Path = firstMedia.Path,
IsInMixedFolder = isInMixedFolder,
@@ -230,12 +220,12 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
return result;
}
private bool ContainsFile(List<AudioBookInfo> result, FileSystemMetadata file)
private static bool ContainsFile(IEnumerable<AudioBookInfo> result, FileSystemMetadata file)
{
return result.Any(i => ContainsFile(i, file));
}
private bool ContainsFile(AudioBookInfo result, FileSystemMetadata file)
private static bool ContainsFile(AudioBookInfo result, FileSystemMetadata file)
{
return result.Files.Any(i => ContainsFile(i, file)) ||
result.AlternateVersions.Any(i => ContainsFile(i, file)) ||
@@ -246,10 +236,5 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
{
return string.Equals(result.Path, file.FullName, StringComparison.OrdinalIgnoreCase);
}
private static bool IsIgnored(string filename)
{
return false;
}
}
}

View File

@@ -6,6 +6,7 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Emby.Naming.Audio;
using Emby.Naming.Common;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
@@ -22,20 +23,17 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
public class MusicAlbumResolver : ItemResolver<MusicAlbum>
{
private readonly ILogger<MusicAlbumResolver> _logger;
private readonly IFileSystem _fileSystem;
private readonly ILibraryManager _libraryManager;
private readonly NamingOptions _namingOptions;
/// <summary>
/// Initializes a new instance of the <see cref="MusicAlbumResolver"/> class.
/// </summary>
/// <param name="logger">The logger.</param>
/// <param name="fileSystem">The file system.</param>
/// <param name="libraryManager">The library manager.</param>
public MusicAlbumResolver(ILogger<MusicAlbumResolver> logger, IFileSystem fileSystem, ILibraryManager libraryManager)
/// <param name="namingOptions">The naming options.</param>
public MusicAlbumResolver(ILogger<MusicAlbumResolver> logger, NamingOptions namingOptions)
{
_logger = logger;
_fileSystem = fileSystem;
_libraryManager = libraryManager;
_namingOptions = namingOptions;
}
/// <summary>
@@ -87,7 +85,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
/// <returns><c>true</c> if the provided path points to a music album, <c>false</c> otherwise.</returns>
public bool IsMusicAlbum(string path, IDirectoryService directoryService)
{
return ContainsMusic(directoryService.GetFileSystemEntries(path), true, directoryService, _logger, _fileSystem, _libraryManager);
return ContainsMusic(directoryService.GetFileSystemEntries(path), true, directoryService);
}
/// <summary>
@@ -101,7 +99,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
if (args.IsDirectory)
{
// if (args.Parent is MusicArtist) return true; // saves us from testing children twice
if (ContainsMusic(args.FileSystemChildren, true, args.DirectoryService, _logger, _fileSystem, _libraryManager))
if (ContainsMusic(args.FileSystemChildren, true, args.DirectoryService))
{
return true;
}
@@ -114,15 +112,12 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
/// Determine if the supplied list contains what we should consider music.
/// </summary>
private bool ContainsMusic(
IEnumerable<FileSystemMetadata> list,
ICollection<FileSystemMetadata> list,
bool allowSubfolders,
IDirectoryService directoryService,
ILogger<MusicAlbumResolver> logger,
IFileSystem fileSystem,
ILibraryManager libraryManager)
IDirectoryService directoryService)
{
// check for audio files before digging down into directories
var foundAudioFile = list.Any(fileSystemInfo => !fileSystemInfo.IsDirectory && libraryManager.IsAudioFile(fileSystemInfo.FullName));
var foundAudioFile = list.Any(fileSystemInfo => !fileSystemInfo.IsDirectory && AudioFileParser.IsAudioFile(fileSystemInfo.FullName, _namingOptions));
if (foundAudioFile)
{
// at least one audio file exists
@@ -137,21 +132,20 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
var discSubfolderCount = 0;
var namingOptions = ((LibraryManager)_libraryManager).GetNamingOptions();
var parser = new AlbumParser(namingOptions);
var parser = new AlbumParser(_namingOptions);
var directories = list.Where(fileSystemInfo => fileSystemInfo.IsDirectory);
var result = Parallel.ForEach(directories, (fileSystemInfo, state) =>
{
var path = fileSystemInfo.FullName;
var hasMusic = ContainsMusic(directoryService.GetFileSystemEntries(path), false, directoryService, logger, fileSystem, libraryManager);
var hasMusic = ContainsMusic(directoryService.GetFileSystemEntries(path), false, directoryService);
if (hasMusic)
{
if (parser.IsMultiPart(path))
{
logger.LogDebug("Found multi-disc folder: {Path}", path);
_logger.LogDebug("Found multi-disc folder: {Path}", path);
Interlocked.Increment(ref discSubfolderCount);
}
else

View File

@@ -3,12 +3,11 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using MediaBrowser.Controller.Configuration;
using Emby.Naming.Common;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Resolvers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Library.Resolvers.Audio
@@ -19,27 +18,19 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
public class MusicArtistResolver : ItemResolver<MusicArtist>
{
private readonly ILogger<MusicAlbumResolver> _logger;
private readonly IFileSystem _fileSystem;
private readonly ILibraryManager _libraryManager;
private readonly IServerConfigurationManager _config;
private NamingOptions _namingOptions;
/// <summary>
/// Initializes a new instance of the <see cref="MusicArtistResolver"/> class.
/// </summary>
/// <param name="logger">The logger for the created <see cref="MusicAlbumResolver"/> instances.</param>
/// <param name="fileSystem">The file system.</param>
/// <param name="libraryManager">The library manager.</param>
/// <param name="config">The configuration manager.</param>
/// <param name="namingOptions">The naming options.</param>
public MusicArtistResolver(
ILogger<MusicAlbumResolver> logger,
IFileSystem fileSystem,
ILibraryManager libraryManager,
IServerConfigurationManager config)
NamingOptions namingOptions)
{
_logger = logger;
_fileSystem = fileSystem;
_libraryManager = libraryManager;
_config = config;
_namingOptions = namingOptions;
}
/// <summary>
@@ -89,7 +80,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
var directoryService = args.DirectoryService;
var albumResolver = new MusicAlbumResolver(_logger, _fileSystem, _libraryManager);
var albumResolver = new MusicAlbumResolver(_logger, _namingOptions);
// If we contain an album assume we are an artist folder
var directories = args.FileSystemChildren.Where(i => i.IsDirectory);

View File

@@ -6,6 +6,7 @@ using System;
using System.IO;
using System.Linq;
using DiscUtils.Udf;
using Emby.Naming.Common;
using Emby.Naming.Video;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
@@ -21,12 +22,12 @@ namespace Emby.Server.Implementations.Library.Resolvers
public abstract class BaseVideoResolver<T> : MediaBrowser.Controller.Resolvers.ItemResolver<T>
where T : Video, new()
{
protected BaseVideoResolver(ILibraryManager libraryManager)
protected BaseVideoResolver(NamingOptions namingOptions)
{
LibraryManager = libraryManager;
NamingOptions = namingOptions;
}
protected ILibraryManager LibraryManager { get; }
protected NamingOptions NamingOptions { get; }
/// <summary>
/// Resolves the specified args.
@@ -48,120 +49,71 @@ namespace Emby.Server.Implementations.Library.Resolvers
protected virtual TVideoType ResolveVideo<TVideoType>(ItemResolveArgs args, bool parseName)
where TVideoType : Video, new()
{
var namingOptions = LibraryManager.GetNamingOptions();
VideoFileInfo videoInfo = null;
VideoType? videoType = null;
// If the path is a file check for a matching extensions
if (args.IsDirectory)
{
TVideoType video = null;
VideoFileInfo videoInfo = null;
// Loop through each child file/folder and see if we find a video
foreach (var child in args.FileSystemChildren)
{
var filename = child.Name;
if (child.IsDirectory)
{
if (IsDvdDirectory(child.FullName, filename, args.DirectoryService))
{
videoInfo = VideoResolver.ResolveDirectory(args.Path, namingOptions);
if (videoInfo == null)
{
return null;
}
video = new TVideoType
{
Path = args.Path,
VideoType = VideoType.Dvd,
ProductionYear = videoInfo.Year
};
break;
videoType = VideoType.Dvd;
}
if (IsBluRayDirectory(filename))
else if (IsBluRayDirectory(filename))
{
videoInfo = VideoResolver.ResolveDirectory(args.Path, namingOptions);
if (videoInfo == null)
{
return null;
}
video = new TVideoType
{
Path = args.Path,
VideoType = VideoType.BluRay,
ProductionYear = videoInfo.Year
};
break;
videoType = VideoType.BluRay;
}
}
else if (IsDvdFile(filename))
{
videoInfo = VideoResolver.ResolveDirectory(args.Path, namingOptions);
if (videoInfo == null)
{
return null;
}
video = new TVideoType
{
Path = args.Path,
VideoType = VideoType.Dvd,
ProductionYear = videoInfo.Year
};
break;
videoType = VideoType.Dvd;
}
if (videoType == null)
{
continue;
}
videoInfo = VideoResolver.ResolveDirectory(args.Path, NamingOptions, parseName);
break;
}
if (video != null)
{
video.Name = parseName ?
videoInfo.Name :
Path.GetFileName(args.Path);
Set3DFormat(video, videoInfo);
}
return video;
}
else
{
var videoInfo = VideoResolver.Resolve(args.Path, false, namingOptions, false);
if (videoInfo == null)
{
return null;
}
if (LibraryManager.IsVideoFile(args.Path) || videoInfo.IsStub)
{
var path = args.Path;
var video = new TVideoType
{
Path = path,
IsInMixedFolder = true,
ProductionYear = videoInfo.Year
};
SetVideoType(video, videoInfo);
video.Name = parseName ?
videoInfo.Name :
Path.GetFileNameWithoutExtension(args.Path);
Set3DFormat(video, videoInfo);
return video;
}
videoInfo = VideoResolver.Resolve(args.Path, false, NamingOptions, parseName);
}
return null;
if (videoInfo == null || (!videoInfo.IsStub && !VideoResolver.IsVideoFile(args.Path, NamingOptions)))
{
return null;
}
var video = new TVideoType
{
Name = videoInfo.Name,
Path = args.Path,
ProductionYear = videoInfo.Year,
ExtraType = videoInfo.ExtraType
};
if (videoType.HasValue)
{
video.VideoType = videoType.Value;
}
else
{
SetVideoType(video, videoInfo);
}
Set3DFormat(video, videoInfo);
return video;
}
protected void SetVideoType(Video video, VideoFileInfo videoInfo)
@@ -206,8 +158,8 @@ namespace Emby.Server.Implementations.Library.Resolvers
{
// 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))
{
UdfReader udfReader = new UdfReader(videoFileStream);
if (udfReader.DirectoryExists("VIDEO_TS"))
{
video.IsoType = IsoType.Dvd;
@@ -267,7 +219,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
protected void Set3DFormat(Video video)
{
var result = Format3DParser.Parse(video.Path, LibraryManager.GetNamingOptions());
var result = Format3DParser.Parse(video.Path, NamingOptions);
Set3DFormat(video, result.Is3D, result.Format3D);
}

View File

@@ -5,6 +5,7 @@
using System;
using System.IO;
using System.Linq;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Entities;
@@ -32,7 +33,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Books
var extension = Path.GetExtension(args.Path);
if (extension != null && _validExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
if (extension != null && _validExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase))
{
// It's a book
return new Book

View File

@@ -0,0 +1,93 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using Emby.Naming.Common;
using Emby.Naming.Video;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Resolvers;
using MediaBrowser.Model.Entities;
using static Emby.Naming.Video.ExtraRuleResolver;
namespace Emby.Server.Implementations.Library.Resolvers
{
/// <summary>
/// Resolves a Path into a Video or Video subclass.
/// </summary>
internal class ExtraResolver
{
private readonly NamingOptions _namingOptions;
private readonly IItemResolver[] _trailerResolvers;
private readonly IItemResolver[] _videoResolvers;
/// <summary>
/// Initializes an new instance of the <see cref="ExtraResolver"/> class.
/// </summary>
/// <param name="namingOptions">An instance of <see cref="NamingOptions"/>.</param>
public ExtraResolver(NamingOptions namingOptions)
{
_namingOptions = namingOptions;
_trailerResolvers = new IItemResolver[] { new GenericVideoResolver<Trailer>(namingOptions) };
_videoResolvers = new IItemResolver[] { new GenericVideoResolver<Video>(namingOptions) };
}
/// <summary>
/// Gets the resolvers for the extra type.
/// </summary>
/// <param name="extraType">The extra type.</param>
/// <returns>The resolvers for the extra type.</returns>
public IItemResolver[]? GetResolversForExtraType(ExtraType extraType) => extraType switch
{
ExtraType.Trailer => _trailerResolvers,
// For audio we'll have to rely on the AudioResolver, which is a "built-in"
ExtraType.ThemeSong => null,
_ => _videoResolvers
};
public bool TryGetExtraTypeForOwner(string path, VideoFileInfo ownerVideoFileInfo, [NotNullWhen(true)] out ExtraType? extraType)
{
var extraResult = GetExtraInfo(path, _namingOptions);
if (extraResult.ExtraType == null)
{
extraType = null;
return false;
}
var cleanDateTimeResult = CleanDateTimeParser.Clean(Path.GetFileNameWithoutExtension(path), _namingOptions.CleanDateTimeRegexes);
var name = cleanDateTimeResult.Name;
var year = cleanDateTimeResult.Year;
var parentDir = ownerVideoFileInfo.IsDirectory ? ownerVideoFileInfo.Path : Path.GetDirectoryName(ownerVideoFileInfo.Path.AsSpan());
var trimmedFileNameWithoutExtension = TrimFilenameDelimiters(ownerVideoFileInfo.FileNameWithoutExtension, _namingOptions.VideoFlagDelimiters);
var trimmedVideoInfoName = TrimFilenameDelimiters(ownerVideoFileInfo.Name, _namingOptions.VideoFlagDelimiters);
var trimmedExtraFileName = TrimFilenameDelimiters(name, _namingOptions.VideoFlagDelimiters);
// first check filenames
bool isValid = StartsWith(trimmedExtraFileName, trimmedFileNameWithoutExtension)
|| (StartsWith(trimmedExtraFileName, trimmedVideoInfoName) && year == ownerVideoFileInfo.Year);
if (!isValid)
{
// When the extra rule type is DirectoryName we must go one level higher to get the "real" dir name
var currentParentDir = extraResult.Rule?.RuleType == ExtraRuleType.DirectoryName
? Path.GetDirectoryName(Path.GetDirectoryName(path.AsSpan()))
: Path.GetDirectoryName(path.AsSpan());
isValid = !currentParentDir.IsEmpty && !parentDir.IsEmpty && currentParentDir.Equals(parentDir, StringComparison.OrdinalIgnoreCase);
}
extraType = extraResult.ExtraType;
return isValid;
}
private static ReadOnlySpan<char> TrimFilenameDelimiters(ReadOnlySpan<char> name, ReadOnlySpan<char> videoFlagDelimiters)
{
return name.IsEmpty ? name : name.TrimEnd().TrimEnd(videoFlagDelimiters).TrimEnd();
}
private static bool StartsWith(ReadOnlySpan<char> fileName, ReadOnlySpan<char> baseName)
{
return !baseName.IsEmpty && fileName.StartsWith(baseName, StringComparison.OrdinalIgnoreCase);
}
}
}

View File

@@ -1,17 +1,23 @@
#nullable disable
#pragma warning disable CS1591
#nullable disable
using Emby.Naming.Common;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
namespace Emby.Server.Implementations.Library.Resolvers
{
/// <summary>
/// Resolves a Path into an instance of the <see cref="Video"/> class.
/// </summary>
/// <typeparam name="T">The type of item to resolve.</typeparam>
public class GenericVideoResolver<T> : BaseVideoResolver<T>
where T : Video, new()
{
public GenericVideoResolver(ILibraryManager libraryManager)
: base(libraryManager)
/// <summary>
/// Initializes a new instance of the <see cref="GenericVideoResolver{T}"/> class.
/// </summary>
/// <param name="namingOptions">The naming options.</param>
public GenericVideoResolver(NamingOptions namingOptions)
: base(namingOptions)
{
}
}

View File

@@ -65,7 +65,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
private static void SetProviderIdFromPath(BaseItem item)
{
// we need to only look at the name of this actual item (not parents)
var justName = Path.GetFileName(item.Path);
var justName = Path.GetFileName(item.Path.AsSpan());
var id = justName.GetAttributeValue("tmdbid");

View File

@@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Emby.Naming.Common;
using Emby.Naming.Video;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Drawing;
@@ -38,10 +39,10 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
/// <summary>
/// Initializes a new instance of the <see cref="MovieResolver"/> class.
/// </summary>
/// <param name="libraryManager">The library manager.</param>
/// <param name="imageProcessor">The image processor.</param>
public MovieResolver(ILibraryManager libraryManager, IImageProcessor imageProcessor)
: base(libraryManager)
/// <param name="namingOptions">The naming options.</param>
public MovieResolver(IImageProcessor imageProcessor, NamingOptions namingOptions)
: base(namingOptions)
{
_imageProcessor = imageProcessor;
}
@@ -59,7 +60,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
string collectionType,
IDirectoryService directoryService)
{
var result = ResolveMultipleInternal(parent, files, collectionType, directoryService);
var result = ResolveMultipleInternal(parent, files, collectionType);
if (result != null)
{
@@ -89,26 +90,24 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
return null;
}
var files = args.FileSystemChildren
.Where(i => !LibraryManager.IgnoreFile(i, args.Parent))
.ToList();
Video movie = null;
var files = args.GetActualFileSystemChildren().ToList();
if (string.Equals(collectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase))
{
return FindMovie<MusicVideo>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, false);
movie = FindMovie<MusicVideo>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, false);
}
if (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase))
{
return FindMovie<Video>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, false);
movie = FindMovie<Video>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, false);
}
if (string.IsNullOrEmpty(collectionType))
{
// Owned items will be caught by the plain video resolver
// Owned items will be caught by the video extra resolver
if (args.Parent == null)
{
// return FindMovie<Video>(args.Path, args.Parent, files, args.DirectoryService, collectionType);
return null;
}
@@ -117,23 +116,22 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
return null;
}
{
return FindMovie<Movie>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, true);
}
movie = FindMovie<Movie>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, true);
}
if (string.Equals(collectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase))
{
return FindMovie<Movie>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, true);
movie = FindMovie<Movie>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, true);
}
return null;
// ignore extras
return movie?.ExtraType == null ? movie : null;
}
// Handle owned items
// Owned items will be caught by the video extra resolver
if (args.Parent == null)
{
return base.Resolve(args);
return null;
}
if (IsInvalid(args.Parent, collectionType))
@@ -168,6 +166,12 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
item = ResolveVideo<Video>(args, false);
}
// Ignore extras
if (item?.ExtraType != null)
{
return null;
}
if (item != null)
{
item.IsInMixedFolder = true;
@@ -179,8 +183,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
private MultiItemResolverResult ResolveMultipleInternal(
Folder parent,
List<FileSystemMetadata> files,
string collectionType,
IDirectoryService directoryService)
string collectionType)
{
if (IsInvalid(parent, collectionType))
{
@@ -189,13 +192,13 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
if (string.Equals(collectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase))
{
return ResolveVideos<MusicVideo>(parent, files, directoryService, true, collectionType, false);
return ResolveVideos<MusicVideo>(parent, files, true, collectionType, false);
}
if (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase) ||
string.Equals(collectionType, CollectionType.Photos, StringComparison.OrdinalIgnoreCase))
{
return ResolveVideos<Video>(parent, files, directoryService, false, collectionType, false);
return ResolveVideos<Video>(parent, files, false, collectionType, false);
}
if (string.IsNullOrEmpty(collectionType))
@@ -203,7 +206,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
// Owned items should just use the plain video type
if (parent == null)
{
return ResolveVideos<Video>(parent, files, directoryService, false, collectionType, false);
return ResolveVideos<Video>(parent, files, false, collectionType, false);
}
if (parent is Series || parent.GetParents().OfType<Series>().Any())
@@ -211,12 +214,12 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
return null;
}
return ResolveVideos<Movie>(parent, files, directoryService, false, collectionType, true);
return ResolveVideos<Movie>(parent, files, false, collectionType, true);
}
if (string.Equals(collectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase))
{
return ResolveVideos<Movie>(parent, files, directoryService, true, collectionType, true);
return ResolveVideos<Movie>(parent, files, true, collectionType, true);
}
return null;
@@ -225,21 +228,20 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
private MultiItemResolverResult ResolveVideos<T>(
Folder parent,
IEnumerable<FileSystemMetadata> fileSystemEntries,
IDirectoryService directoryService,
bool suppportMultiEditions,
bool supportMultiEditions,
string collectionType,
bool parseName)
where T : Video, new()
{
var files = new List<FileSystemMetadata>();
var videos = new List<BaseItem>();
var leftOver = new List<FileSystemMetadata>();
var hasCollectionType = !string.IsNullOrEmpty(collectionType);
// Loop through each child file/folder and see if we find a video
foreach (var child in fileSystemEntries)
{
// This is a hack but currently no better way to resolve a sometimes ambiguous situation
if (string.IsNullOrEmpty(collectionType))
if (!hasCollectionType)
{
if (string.Equals(child.Name, "tvshow.nfo", StringComparison.OrdinalIgnoreCase)
|| string.Equals(child.Name, "season.nfo", StringComparison.OrdinalIgnoreCase))
@@ -258,31 +260,39 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
}
}
var namingOptions = LibraryManager.GetNamingOptions();
var videoInfos = files
.Select(i => VideoResolver.Resolve(i.FullName, i.IsDirectory, NamingOptions, parseName))
.Where(f => f != null)
.ToList();
var resolverResult = VideoListResolver.Resolve(files, namingOptions, suppportMultiEditions).ToList();
var resolverResult = VideoListResolver.Resolve(videoInfos, NamingOptions, supportMultiEditions, parseName);
var result = new MultiItemResolverResult
{
ExtraFiles = leftOver,
Items = videos
ExtraFiles = leftOver
};
var isInMixedFolder = resolverResult.Count > 1 || (parent != null && parent.IsTopParent);
var isInMixedFolder = resolverResult.Count > 1 || parent?.IsTopParent == true;
foreach (var video in resolverResult)
{
var firstVideo = video.Files[0];
var path = firstVideo.Path;
if (video.ExtraType != null)
{
result.ExtraFiles.Add(files.Find(f => string.Equals(f.FullName, path, StringComparison.OrdinalIgnoreCase)));
continue;
}
var additionalParts = video.Files.Count > 1 ? video.Files.Skip(1).Select(i => i.Path).ToArray() : Array.Empty<string>();
var videoItem = new T
{
Path = video.Files[0].Path,
Path = path,
IsInMixedFolder = isInMixedFolder,
ProductionYear = video.Year,
Name = parseName ?
video.Name :
Path.GetFileNameWithoutExtension(video.Files[0].Path),
AdditionalParts = video.Files.Skip(1).Select(i => i.Path).ToArray(),
Name = parseName ? video.Name : firstVideo.Name,
AdditionalParts = additionalParts,
LocalAlternateVersions = video.AlternateVersions.Select(i => i.Path).ToArray()
};
@@ -300,21 +310,34 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
private static bool IsIgnored(string filename)
{
// Ignore samples
Match m = Regex.Match(filename, @"\bsample\b", RegexOptions.IgnoreCase);
Match m = Regex.Match(filename, @"\bsample\b", RegexOptions.IgnoreCase | RegexOptions.Compiled);
return m.Success;
}
private bool ContainsFile(List<VideoInfo> result, FileSystemMetadata file)
private static bool ContainsFile(IReadOnlyList<VideoInfo> result, FileSystemMetadata file)
{
return result.Any(i => ContainsFile(i, file));
}
for (var i = 0; i < result.Count; i++)
{
var current = result[i];
for (var j = 0; j < current.Files.Count; j++)
{
if (ContainsFile(current.Files[j], file))
{
return true;
}
}
private bool ContainsFile(VideoInfo result, FileSystemMetadata file)
{
return result.Files.Any(i => ContainsFile(i, file)) ||
result.AlternateVersions.Any(i => ContainsFile(i, file)) ||
result.Extras.Any(i => ContainsFile(i, file));
for (var j = 0; j < current.AlternateVersions.Count; j++)
{
if (ContainsFile(current.AlternateVersions[j], file))
{
return true;
}
}
}
return false;
}
private static bool ContainsFile(VideoFileInfo result, FileSystemMetadata file)
@@ -343,9 +366,9 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
if (item is Movie || item is MusicVideo)
{
// We need to only look at the name of this actual item (not parents)
var justName = item.IsInMixedFolder ? Path.GetFileName(item.Path) : Path.GetFileName(item.ContainingFolderPath);
var justName = item.IsInMixedFolder ? Path.GetFileName(item.Path.AsSpan()) : Path.GetFileName(item.ContainingFolderPath.AsSpan());
if (!string.IsNullOrEmpty(justName))
if (!justName.IsEmpty)
{
// check for tmdb id
var tmdbid = justName.GetAttributeValue("tmdbid");
@@ -359,7 +382,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
if (!string.IsNullOrEmpty(item.Path))
{
// check for imdb id - we use full media path, as we can assume, that this will match in any use case (wither id in parent dir or in file name)
var imdbid = item.Path.GetAttributeValue("imdbid");
var imdbid = item.Path.AsSpan().GetAttributeValue("imdbid");
if (!string.IsNullOrWhiteSpace(imdbid))
{
@@ -432,13 +455,13 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
// TODO: Allow GetMultiDiscMovie in here
const bool SupportsMultiVersion = true;
var result = ResolveVideos<T>(parent, fileSystemEntries, directoryService, SupportsMultiVersion, collectionType, parseName) ??
var result = ResolveVideos<T>(parent, fileSystemEntries, SupportsMultiVersion, collectionType, parseName) ??
new MultiItemResolverResult();
if (result.Items.Count == 1)
{
var videoPath = result.Items[0].Path;
var hasPhotos = photos.Any(i => !PhotoResolver.IsOwnedByResolvedMedia(LibraryManager, videoPath, i.Name));
var hasPhotos = photos.Any(i => !PhotoResolver.IsOwnedByResolvedMedia(videoPath, i.Name));
if (!hasPhotos)
{
@@ -511,9 +534,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
return null;
}
var namingOptions = ((LibraryManager)LibraryManager).GetNamingOptions();
var result = new StackResolver(namingOptions).ResolveDirectories(folderPaths).ToList();
var result = StackResolver.ResolveDirectories(folderPaths, NamingOptions).ToList();
if (result.Count != 1)
{

View File

@@ -1,6 +1,7 @@
#nullable disable
using System;
using Emby.Naming.Common;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
@@ -15,17 +16,17 @@ namespace Emby.Server.Implementations.Library.Resolvers
public class PhotoAlbumResolver : GenericFolderResolver<PhotoAlbum>
{
private readonly IImageProcessor _imageProcessor;
private readonly ILibraryManager _libraryManager;
private readonly NamingOptions _namingOptions;
/// <summary>
/// Initializes a new instance of the <see cref="PhotoAlbumResolver"/> class.
/// </summary>
/// <param name="imageProcessor">The image processor.</param>
/// <param name="libraryManager">The library manager.</param>
public PhotoAlbumResolver(IImageProcessor imageProcessor, ILibraryManager libraryManager)
/// <param name="namingOptions">The naming options.</param>
public PhotoAlbumResolver(IImageProcessor imageProcessor, NamingOptions namingOptions)
{
_imageProcessor = imageProcessor;
_libraryManager = libraryManager;
_namingOptions = namingOptions;
}
/// <inheritdoc />
@@ -73,7 +74,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
foreach (var siblingFile in files)
{
if (PhotoResolver.IsOwnedByMedia(_libraryManager, siblingFile.FullName, filename))
if (PhotoResolver.IsOwnedByMedia(_namingOptions, siblingFile.FullName, filename))
{
ownedByMedia = true;
break;

View File

@@ -6,6 +6,9 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Emby.Naming.Common;
using Emby.Naming.Video;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
@@ -16,7 +19,8 @@ namespace Emby.Server.Implementations.Library.Resolvers
public class PhotoResolver : ItemResolver<Photo>
{
private readonly IImageProcessor _imageProcessor;
private readonly ILibraryManager _libraryManager;
private readonly NamingOptions _namingOptions;
private static readonly HashSet<string> _ignoreFiles = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
"folder",
@@ -30,10 +34,10 @@ namespace Emby.Server.Implementations.Library.Resolvers
"default"
};
public PhotoResolver(IImageProcessor imageProcessor, ILibraryManager libraryManager)
public PhotoResolver(IImageProcessor imageProcessor, NamingOptions namingOptions)
{
_imageProcessor = imageProcessor;
_libraryManager = libraryManager;
_namingOptions = namingOptions;
}
/// <summary>
@@ -60,7 +64,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
foreach (var file in files)
{
if (IsOwnedByMedia(_libraryManager, file.FullName, filename))
if (IsOwnedByMedia(_namingOptions, file.FullName, filename))
{
return null;
}
@@ -77,17 +81,12 @@ namespace Emby.Server.Implementations.Library.Resolvers
return null;
}
internal static bool IsOwnedByMedia(ILibraryManager libraryManager, string file, string imageFilename)
internal static bool IsOwnedByMedia(NamingOptions namingOptions, string file, string imageFilename)
{
if (libraryManager.IsVideoFile(file))
{
return IsOwnedByResolvedMedia(libraryManager, file, imageFilename);
}
return false;
return VideoResolver.IsVideoFile(file, namingOptions) && IsOwnedByResolvedMedia(file, imageFilename);
}
internal static bool IsOwnedByResolvedMedia(ILibraryManager libraryManager, string file, string imageFilename)
internal static bool IsOwnedByResolvedMedia(string file, string imageFilename)
=> imageFilename.StartsWith(Path.GetFileNameWithoutExtension(file), StringComparison.OrdinalIgnoreCase);
internal static bool IsImageFile(string path, IImageProcessor imageProcessor)
@@ -110,7 +109,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
}
string extension = Path.GetExtension(path).TrimStart('.');
return imageProcessor.SupportedInputFormats.Contains(extension, StringComparer.OrdinalIgnoreCase);
return imageProcessor.SupportedInputFormats.Contains(extension, StringComparison.OrdinalIgnoreCase);
}
}
}

View File

@@ -5,6 +5,7 @@
using System;
using System.IO;
using System.Linq;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Playlists;
using MediaBrowser.Controller.Resolvers;
@@ -57,10 +58,10 @@ namespace Emby.Server.Implementations.Library.Resolvers
// Check if this is a music playlist file
// It should have the correct collection type and a supported file extension
else if (_musicPlaylistCollectionTypes.Contains(args.CollectionType ?? string.Empty, StringComparer.OrdinalIgnoreCase))
else if (_musicPlaylistCollectionTypes.Contains(args.CollectionType ?? string.Empty, StringComparison.OrdinalIgnoreCase))
{
var extension = Path.GetExtension(args.Path);
if (Playlist.SupportedExtensions.Contains(extension ?? string.Empty, StringComparer.OrdinalIgnoreCase))
if (Playlist.SupportedExtensions.Contains(extension ?? string.Empty, StringComparison.OrdinalIgnoreCase))
{
return new Playlist
{

View File

@@ -2,7 +2,7 @@
using System;
using System.Linq;
using MediaBrowser.Controller.Entities;
using Emby.Naming.Common;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Entities;
@@ -17,9 +17,9 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
/// <summary>
/// Initializes a new instance of the <see cref="EpisodeResolver"/> class.
/// </summary>
/// <param name="libraryManager">The library manager.</param>
public EpisodeResolver(ILibraryManager libraryManager)
: base(libraryManager)
/// <param name="namingOptions">The naming options.</param>
public EpisodeResolver(NamingOptions namingOptions)
: base(namingOptions)
{
}
@@ -44,34 +44,36 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
// If the parent is a Season or Series and the parent is not an extras folder, then this is an Episode if the VideoResolver returns something
// Also handle flat tv folders
if ((season != null ||
string.Equals(args.GetCollectionType(), CollectionType.TvShows, StringComparison.OrdinalIgnoreCase) ||
args.HasParent<Series>())
&& (parent is Series || !BaseItem.AllExtrasTypesFolderNames.ContainsKey(parent.Name)))
if (season != null ||
string.Equals(args.GetCollectionType(), CollectionType.TvShows, StringComparison.OrdinalIgnoreCase) ||
args.HasParent<Series>())
{
var episode = ResolveVideo<Episode>(args, false);
if (episode != null)
// Ignore extras
if (episode == null || episode.ExtraType != null)
{
var series = parent as Series ?? parent.GetParents().OfType<Series>().FirstOrDefault();
return null;
}
if (series != null)
{
episode.SeriesId = series.Id;
episode.SeriesName = series.Name;
}
var series = parent as Series ?? parent.GetParents().OfType<Series>().FirstOrDefault();
if (season != null)
{
episode.SeasonId = season.Id;
episode.SeasonName = season.Name;
}
if (series != null)
{
episode.SeriesId = series.Id;
episode.SeriesName = series.Name;
}
// Assume season 1 if there's no season folder and a season number could not be determined
if (season == null && !episode.ParentIndexNumber.HasValue && (episode.IndexNumber.HasValue || episode.PremiereDate.HasValue))
{
episode.ParentIndexNumber = 1;
}
if (season != null)
{
episode.SeasonId = season.Id;
episode.SeasonName = season.Name;
}
// Assume season 1 if there's no season folder and a season number could not be determined
if (season == null && !episode.ParentIndexNumber.HasValue && (episode.IndexNumber.HasValue || episode.PremiereDate.HasValue))
{
episode.ParentIndexNumber = 1;
}
return episode;

View File

@@ -1,6 +1,7 @@
#nullable disable
using System.Globalization;
using Emby.Naming.Common;
using Emby.Naming.TV;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
@@ -14,22 +15,22 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
/// </summary>
public class SeasonResolver : GenericFolderResolver<Season>
{
private readonly ILibraryManager _libraryManager;
private readonly ILocalizationManager _localization;
private readonly ILogger<SeasonResolver> _logger;
private readonly NamingOptions _namingOptions;
/// <summary>
/// Initializes a new instance of the <see cref="SeasonResolver"/> class.
/// </summary>
/// <param name="libraryManager">The library manager.</param>
/// <param name="namingOptions">The naming options.</param>
/// <param name="localization">The localization.</param>
/// <param name="logger">The logger.</param>
public SeasonResolver(
ILibraryManager libraryManager,
NamingOptions namingOptions,
ILocalizationManager localization,
ILogger<SeasonResolver> logger)
{
_libraryManager = libraryManager;
_namingOptions = namingOptions;
_localization = localization;
_logger = logger;
}
@@ -43,7 +44,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
{
if (args.Parent is Series series && args.IsDirectory)
{
var namingOptions = ((LibraryManager)_libraryManager).GetNamingOptions();
var namingOptions = _namingOptions;
var path = args.Path;
@@ -65,18 +66,15 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
var episodeInfo = resolver.Resolve(testPath, true);
if (episodeInfo != null)
if (episodeInfo?.EpisodeNumber != null && episodeInfo.SeasonNumber.HasValue)
{
if (episodeInfo.EpisodeNumber.HasValue && episodeInfo.SeasonNumber.HasValue)
{
_logger.LogDebug(
"Found folder underneath series with episode number: {0}. Season {1}. Episode {2}",
path,
episodeInfo.SeasonNumber.Value,
episodeInfo.EpisodeNumber.Value);
_logger.LogDebug(
"Found folder underneath series with episode number: {0}. Season {1}. Episode {2}",
path,
episodeInfo.SeasonNumber.Value,
episodeInfo.EpisodeNumber.Value);
return null;
}
return null;
}
}

View File

@@ -5,7 +5,9 @@
using System;
using System.Collections.Generic;
using System.IO;
using Emby.Naming.Common;
using Emby.Naming.TV;
using Emby.Naming.Video;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Resolvers;
@@ -21,17 +23,17 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
public class SeriesResolver : GenericFolderResolver<Series>
{
private readonly ILogger<SeriesResolver> _logger;
private readonly ILibraryManager _libraryManager;
private readonly NamingOptions _namingOptions;
/// <summary>
/// Initializes a new instance of the <see cref="SeriesResolver"/> class.
/// </summary>
/// <param name="logger">The logger.</param>
/// <param name="libraryManager">The library manager.</param>
public SeriesResolver(ILogger<SeriesResolver> logger, ILibraryManager libraryManager)
/// <param name="namingOptions">The naming options.</param>
public SeriesResolver(ILogger<SeriesResolver> logger, NamingOptions namingOptions)
{
_logger = logger;
_libraryManager = libraryManager;
_namingOptions = namingOptions;
}
/// <summary>
@@ -54,12 +56,13 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
return null;
}
var seriesInfo = Naming.TV.SeriesResolver.Resolve(_libraryManager.GetNamingOptions(), args.Path);
var seriesInfo = Naming.TV.SeriesResolver.Resolve(_namingOptions, args.Path);
var collectionType = args.GetCollectionType();
if (string.Equals(collectionType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase))
{
var configuredContentType = _libraryManager.GetConfiguredContentType(args.Path);
// TODO refactor into separate class or something, this is copied from LibraryManager.GetConfiguredContentType
var configuredContentType = args.GetConfiguredContentType();
if (!string.Equals(configuredContentType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase))
{
return new Series
@@ -91,7 +94,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
return null;
}
if (IsSeriesFolder(args.Path, args.FileSystemChildren, _logger, _libraryManager, false))
if (IsSeriesFolder(args.Path, args.FileSystemChildren, false))
{
return new Series
{
@@ -105,11 +108,9 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
return null;
}
public static bool IsSeriesFolder(
private bool IsSeriesFolder(
string path,
IEnumerable<FileSystemMetadata> fileSystemChildren,
ILogger<SeriesResolver> logger,
ILibraryManager libraryManager,
bool isTvContentType)
{
foreach (var child in fileSystemChildren)
@@ -118,21 +119,21 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
{
if (IsSeasonFolder(child.FullName, isTvContentType))
{
logger.LogDebug("{Path} is a series because of season folder {Dir}.", path, child.FullName);
_logger.LogDebug("{Path} is a series because of season folder {Dir}.", path, child.FullName);
return true;
}
}
else
{
string fullName = child.FullName;
if (libraryManager.IsVideoFile(fullName))
if (VideoResolver.IsVideoFile(path, _namingOptions))
{
if (isTvContentType)
{
return true;
}
var namingOptions = ((LibraryManager)libraryManager).GetNamingOptions();
var namingOptions = _namingOptions;
var episodeResolver = new Naming.TV.EpisodeResolver(namingOptions);
@@ -145,7 +146,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
}
}
logger.LogDebug("{Path} is not a series folder.", path);
_logger.LogDebug("{Path} is not a series folder.", path);
return false;
}
@@ -181,13 +182,42 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
/// <param name="path">The path.</param>
private static void SetProviderIdFromPath(Series item, string path)
{
var justName = Path.GetFileName(path);
var justName = Path.GetFileName(path.AsSpan());
var id = justName.GetAttributeValue("tvdbid");
if (!string.IsNullOrEmpty(id))
var tvdbId = justName.GetAttributeValue("tvdbid");
if (!string.IsNullOrEmpty(tvdbId))
{
item.SetProviderId(MetadataProvider.Tvdb, id);
item.SetProviderId(MetadataProvider.Tvdb, tvdbId);
}
var tvmazeId = justName.GetAttributeValue("tvmazeid");
if (!string.IsNullOrEmpty(tvmazeId))
{
item.SetProviderId(MetadataProvider.TvMaze, tvmazeId);
}
var tmdbId = justName.GetAttributeValue("tmdbid");
if (!string.IsNullOrEmpty(tmdbId))
{
item.SetProviderId(MetadataProvider.Tmdb, tmdbId);
}
var anidbId = justName.GetAttributeValue("anidbid");
if (!string.IsNullOrEmpty(anidbId))
{
item.SetProviderId("AniDB", anidbId);
}
var aniListId = justName.GetAttributeValue("anilistid");
if (!string.IsNullOrEmpty(aniListId))
{
item.SetProviderId("AniList", aniListId);
}
var aniSearchId = justName.GetAttributeValue("anisearchid");
if (!string.IsNullOrEmpty(aniSearchId))
{
item.SetProviderId("AniSearch", aniSearchId);
}
}
}

View File

@@ -10,12 +10,9 @@ using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Querying;
using MediaBrowser.Model.Search;
using Genre = MediaBrowser.Controller.Entities.Genre;
using Person = MediaBrowser.Controller.Entities.Person;
namespace Emby.Server.Implementations.Library
{
@@ -59,9 +56,9 @@ namespace Emby.Server.Implementations.Library
};
}
private static void AddIfMissing(List<string> list, string value)
private static void AddIfMissing(List<BaseItemKind> list, BaseItemKind value)
{
if (!list.Contains(value, StringComparer.OrdinalIgnoreCase))
if (!list.Contains(value))
{
list.Add(value);
}
@@ -86,63 +83,63 @@ namespace Emby.Server.Implementations.Library
searchTerm = searchTerm.Trim().RemoveDiacritics();
var excludeItemTypes = query.ExcludeItemTypes.ToList();
var includeItemTypes = (query.IncludeItemTypes ?? Array.Empty<string>()).ToList();
var includeItemTypes = (query.IncludeItemTypes ?? Array.Empty<BaseItemKind>()).ToList();
excludeItemTypes.Add(nameof(Year));
excludeItemTypes.Add(nameof(Folder));
excludeItemTypes.Add(BaseItemKind.Year);
excludeItemTypes.Add(BaseItemKind.Folder);
if (query.IncludeGenres && (includeItemTypes.Count == 0 || includeItemTypes.Contains("Genre", StringComparer.OrdinalIgnoreCase)))
if (query.IncludeGenres && (includeItemTypes.Count == 0 || includeItemTypes.Contains(BaseItemKind.Genre)))
{
if (!query.IncludeMedia)
{
AddIfMissing(includeItemTypes, nameof(Genre));
AddIfMissing(includeItemTypes, nameof(MusicGenre));
AddIfMissing(includeItemTypes, BaseItemKind.Genre);
AddIfMissing(includeItemTypes, BaseItemKind.MusicGenre);
}
}
else
{
AddIfMissing(excludeItemTypes, nameof(Genre));
AddIfMissing(excludeItemTypes, nameof(MusicGenre));
AddIfMissing(excludeItemTypes, BaseItemKind.Genre);
AddIfMissing(excludeItemTypes, BaseItemKind.MusicGenre);
}
if (query.IncludePeople && (includeItemTypes.Count == 0 || includeItemTypes.Contains("People", StringComparer.OrdinalIgnoreCase) || includeItemTypes.Contains("Person", StringComparer.OrdinalIgnoreCase)))
if (query.IncludePeople && (includeItemTypes.Count == 0 || includeItemTypes.Contains(BaseItemKind.Person)))
{
if (!query.IncludeMedia)
{
AddIfMissing(includeItemTypes, nameof(Person));
AddIfMissing(includeItemTypes, BaseItemKind.Person);
}
}
else
{
AddIfMissing(excludeItemTypes, nameof(Person));
AddIfMissing(excludeItemTypes, BaseItemKind.Person);
}
if (query.IncludeStudios && (includeItemTypes.Count == 0 || includeItemTypes.Contains("Studio", StringComparer.OrdinalIgnoreCase)))
if (query.IncludeStudios && (includeItemTypes.Count == 0 || includeItemTypes.Contains(BaseItemKind.Studio)))
{
if (!query.IncludeMedia)
{
AddIfMissing(includeItemTypes, nameof(Studio));
AddIfMissing(includeItemTypes, BaseItemKind.Studio);
}
}
else
{
AddIfMissing(excludeItemTypes, nameof(Studio));
AddIfMissing(excludeItemTypes, BaseItemKind.Studio);
}
if (query.IncludeArtists && (includeItemTypes.Count == 0 || includeItemTypes.Contains("MusicArtist", StringComparer.OrdinalIgnoreCase)))
if (query.IncludeArtists && (includeItemTypes.Count == 0 || includeItemTypes.Contains(BaseItemKind.MusicArtist)))
{
if (!query.IncludeMedia)
{
AddIfMissing(includeItemTypes, nameof(MusicArtist));
AddIfMissing(includeItemTypes, BaseItemKind.MusicArtist);
}
}
else
{
AddIfMissing(excludeItemTypes, nameof(MusicArtist));
AddIfMissing(excludeItemTypes, BaseItemKind.MusicArtist);
}
AddIfMissing(excludeItemTypes, nameof(CollectionFolder));
AddIfMissing(excludeItemTypes, nameof(Folder));
AddIfMissing(excludeItemTypes, BaseItemKind.CollectionFolder);
AddIfMissing(excludeItemTypes, BaseItemKind.Folder);
var mediaTypes = query.MediaTypes.ToList();
if (includeItemTypes.Count > 0)
@@ -183,7 +180,7 @@ namespace Emby.Server.Implementations.Library
List<BaseItem> mediaItems;
if (searchQuery.IncludeItemTypes.Length == 1 && string.Equals(searchQuery.IncludeItemTypes[0], "MusicArtist", StringComparison.OrdinalIgnoreCase))
if (searchQuery.IncludeItemTypes.Length == 1 && searchQuery.IncludeItemTypes[0] == BaseItemKind.MusicArtist)
{
if (!searchQuery.ParentId.Equals(Guid.Empty))
{
@@ -192,8 +189,8 @@ namespace Emby.Server.Implementations.Library
searchQuery.ParentId = Guid.Empty;
searchQuery.IncludeItemsByName = true;
searchQuery.IncludeItemTypes = Array.Empty<string>();
mediaItems = _libraryManager.GetAllArtists(searchQuery).Items.Select(i => i.Item1).ToList();
searchQuery.IncludeItemTypes = Array.Empty<BaseItemKind>();
mediaItems = _libraryManager.GetAllArtists(searchQuery).Items.Select(i => i.Item).ToList();
}
else
{

View File

@@ -75,7 +75,7 @@ namespace Emby.Server.Implementations.Library
}
var cacheKey = GetCacheKey(userId, item.Id);
_userData.AddOrUpdate(cacheKey, userData, (k, v) => userData);
_userData.AddOrUpdate(cacheKey, userData, (_, _) => userData);
UserDataSaved?.Invoke(this, new UserDataSaveEventArgs
{
@@ -125,7 +125,7 @@ namespace Emby.Server.Implementations.Library
var cacheKey = GetCacheKey(userId, itemId);
return _userData.GetOrAdd(cacheKey, k => GetUserDataInternal(userId, keys));
return _userData.GetOrAdd(cacheKey, _ => GetUserDataInternal(userId, keys));
}
private UserItemData GetUserDataInternal(long internalUserId, List<string> keys)

View File

@@ -8,11 +8,11 @@ using System.Linq;
using System.Threading;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Model.Channels;
@@ -20,8 +20,6 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Library;
using MediaBrowser.Model.Querying;
using Genre = MediaBrowser.Controller.Entities.Genre;
using Person = MediaBrowser.Controller.Entities.Person;
namespace Emby.Server.Implementations.Library
{
@@ -80,7 +78,7 @@ namespace Emby.Server.Implementations.Library
continue;
}
if (query.PresetViews.Contains(folderViewType ?? string.Empty, StringComparer.OrdinalIgnoreCase))
if (query.PresetViews.Contains(folderViewType ?? string.Empty, StringComparison.OrdinalIgnoreCase))
{
list.Add(GetUserView(folder, folderViewType, string.Empty));
}
@@ -175,12 +173,12 @@ namespace Emby.Server.Implementations.Library
string viewType,
string localizationKey,
string sortName,
Jellyfin.Data.Entities.User user,
User user,
string[] presetViews)
{
if (parents.Count == 1 && parents.All(i => string.Equals(i.CollectionType, viewType, StringComparison.OrdinalIgnoreCase)))
{
if (!presetViews.Contains(viewType, StringComparer.OrdinalIgnoreCase))
if (!presetViews.Contains(viewType, StringComparison.OrdinalIgnoreCase))
{
return (Folder)parents[0];
}
@@ -300,11 +298,11 @@ namespace Emby.Server.Implementations.Library
{
if (hasCollectionType.All(i => string.Equals(i.CollectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase)))
{
includeItemTypes = new string[] { "Movie" };
includeItemTypes = new[] { BaseItemKind.Movie };
}
else if (hasCollectionType.All(i => string.Equals(i.CollectionType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase)))
{
includeItemTypes = new string[] { "Episode" };
includeItemTypes = new[] { BaseItemKind.Episode };
}
}
}
@@ -344,13 +342,13 @@ namespace Emby.Server.Implementations.Library
var excludeItemTypes = includeItemTypes.Length == 0 && mediaTypes.Count == 0
? new[]
{
nameof(Person),
nameof(Studio),
nameof(Year),
nameof(MusicGenre),
nameof(Genre)
BaseItemKind.Person,
BaseItemKind.Studio,
BaseItemKind.Year,
BaseItemKind.MusicGenre,
BaseItemKind.Genre
}
: Array.Empty<string>();
: Array.Empty<BaseItemKind>();
var query = new InternalItemsQuery(user)
{
@@ -361,7 +359,7 @@ namespace Emby.Server.Implementations.Library
(ItemSortBy.SortName, SortOrder.Descending),
(ItemSortBy.ProductionYear, SortOrder.Descending)
},
IsFolder = includeItemTypes.Length == 0 ? false : (bool?)null,
IsFolder = includeItemTypes.Length == 0 ? false : null,
ExcludeItemTypes = excludeItemTypes,
IsVirtualItem = false,
Limit = limit * 5,

View File

@@ -3,6 +3,7 @@ using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
@@ -81,7 +82,7 @@ namespace Emby.Server.Implementations.Library.Validators
var deadEntities = _libraryManager.GetItemList(new InternalItemsQuery
{
IncludeItemTypes = new[] { nameof(MusicArtist) },
IncludeItemTypes = new[] { BaseItemKind.MusicArtist },
IsDeadArtist = true,
IsLocked = false
}).Cast<MusicArtist>().ToList();

View File

@@ -0,0 +1,156 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Collections;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Querying;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Library.Validators
{
/// <summary>
/// Class CollectionPostScanTask.
/// </summary>
public class CollectionPostScanTask : ILibraryPostScanTask
{
private readonly ILibraryManager _libraryManager;
private readonly ICollectionManager _collectionManager;
private readonly ILogger<CollectionPostScanTask> _logger;
/// <summary>
/// Initializes a new instance of the <see cref="CollectionPostScanTask" /> class.
/// </summary>
/// <param name="libraryManager">The library manager.</param>
/// <param name="collectionManager">The collection manager.</param>
/// <param name="logger">The logger.</param>
public CollectionPostScanTask(
ILibraryManager libraryManager,
ICollectionManager collectionManager,
ILogger<CollectionPostScanTask> logger)
{
_libraryManager = libraryManager;
_collectionManager = collectionManager;
_logger = logger;
}
/// <summary>
/// Runs the specified progress.
/// </summary>
/// <param name="progress">The progress.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
public async Task Run(IProgress<double> progress, CancellationToken cancellationToken)
{
var collectionNameMoviesMap = new Dictionary<string, HashSet<Guid>>();
foreach (var library in _libraryManager.RootFolder.Children)
{
if (!_libraryManager.GetLibraryOptions(library).AutomaticallyAddToCollection)
{
continue;
}
var startIndex = 0;
var pagesize = 1000;
while (true)
{
var movies = _libraryManager.GetItemList(new InternalItemsQuery
{
MediaTypes = new string[] { MediaType.Video },
IncludeItemTypes = new[] { BaseItemKind.Movie },
IsVirtualItem = false,
OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) },
Parent = library,
StartIndex = startIndex,
Limit = pagesize,
Recursive = true
});
foreach (var m in movies)
{
if (m is Movie movie && !string.IsNullOrEmpty(movie.CollectionName))
{
if (collectionNameMoviesMap.TryGetValue(movie.CollectionName, out var movieList))
{
movieList.Add(movie.Id);
}
else
{
collectionNameMoviesMap[movie.CollectionName] = new HashSet<Guid> { movie.Id };
}
}
}
if (movies.Count < pagesize)
{
break;
}
startIndex += pagesize;
}
}
var numComplete = 0;
var count = collectionNameMoviesMap.Count;
if (count == 0)
{
progress.Report(100);
return;
}
var boxSets = _libraryManager.GetItemList(new InternalItemsQuery
{
IncludeItemTypes = new[] { BaseItemKind.BoxSet },
CollapseBoxSetItems = false,
Recursive = true
});
foreach (var (collectionName, movieIds) in collectionNameMoviesMap)
{
try
{
var boxSet = boxSets.FirstOrDefault(b => b?.Name == collectionName) as BoxSet;
if (boxSet == null)
{
// won't automatically create collection if only one movie in it
if (movieIds.Count >= 2)
{
boxSet = await _collectionManager.CreateCollectionAsync(new CollectionCreationOptions
{
Name = collectionName,
IsLocked = true
});
await _collectionManager.AddToCollectionAsync(boxSet.Id, movieIds);
}
}
else
{
await _collectionManager.AddToCollectionAsync(boxSet.Id, movieIds);
}
numComplete++;
double percent = numComplete;
percent /= count;
percent *= 100;
progress.Report(percent);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error refreshing {CollectionName} with {@MovieIds}", collectionName, movieIds);
}
}
progress.Report(100);
}
}
}

View File

@@ -2,6 +2,7 @@ using System;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
@@ -91,7 +92,7 @@ namespace Emby.Server.Implementations.Library.Validators
var deadEntities = _libraryManager.GetItemList(new InternalItemsQuery
{
IncludeItemTypes = new[] { nameof(Person) },
IncludeItemTypes = new[] { BaseItemKind.Person },
IsDeadPerson = true,
IsLocked = false
});

Some files were not shown because too many files have changed in this diff Show More