Compare commits

...

191 Commits

Author SHA1 Message Date
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
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
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
Cody Robibero 593b2fd359 Add more speed and more tests 2021-12-09 08:06:06 -07:00
Cody Robibero 3513f5a84b Search for attribute text 2021-12-07 17:10:27 -07: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
Orry Verducci e446e9fde9 Merge branch 'master' into mbaff-interlace-detection 2021-12-01 22:13:52 +00: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
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
Cody Robibero 4890454935 Add additional provider parsing to series file name 2021-11-27 07:44:12 -07:00
Yordany Rodriguez Esquirol 3734c95fd4 Related media according to genre 2021-11-18 15:23:47 -05: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
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
381 changed files with 6547 additions and 5987 deletions
+20
View File
@@ -39,6 +39,14 @@ jobs:
vmImage: 'ubuntu-latest' vmImage: 'ubuntu-latest'
steps: steps:
- script: echo "##vso[task.setvariable variable=JellyfinVersion]$( awk -F '/' '{ print $NF }' <<<'$(Build.SourceBranch)' | sed 's/^v//' )"
displayName: Set release version (stable)
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
- script: './bump-version $(JellyfinVersion)'
displayName: Bump internal version (stable)
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
- script: 'docker build -f deployment/Dockerfile.$(BuildConfiguration) -t jellyfin-server-$(BuildConfiguration) deployment' - script: 'docker build -f deployment/Dockerfile.$(BuildConfiguration) -t jellyfin-server-$(BuildConfiguration) deployment'
displayName: 'Build Dockerfile' displayName: 'Build Dockerfile'
@@ -80,6 +88,14 @@ jobs:
vmImage: 'ubuntu-latest' vmImage: 'ubuntu-latest'
steps: steps:
- script: echo "##vso[task.setvariable variable=JellyfinVersion]$( awk -F '/' '{ print $NF }' <<<'$(Build.SourceBranch)' | sed 's/^v//' )"
displayName: Set release version (stable)
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
- script: './bump-version $(JellyfinVersion)'
displayName: Bump internal version (stable)
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
- task: DownloadPipelineArtifact@2 - task: DownloadPipelineArtifact@2
displayName: 'Download OpenAPI Spec' displayName: 'Download OpenAPI Spec'
inputs: inputs:
@@ -127,6 +143,10 @@ jobs:
displayName: Set release version (stable) displayName: Set release version (stable)
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v') condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
- script: './bump-version $(JellyfinVersion)'
displayName: Bump internal version (stable)
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
- task: Docker@2 - task: Docker@2
displayName: 'Push Unstable Image' displayName: 'Push Unstable Image'
condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master') condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master')
Symlink
+1
View File
@@ -0,0 +1 @@
fedora
-1
View File
@@ -1 +0,0 @@
../fedora/Makefile
+1 -1
View File
@@ -6,7 +6,7 @@
<CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)/jellyfin.ruleset</CodeAnalysisRuleSet> <CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)/jellyfin.ruleset</CodeAnalysisRuleSet>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' "> <PropertyGroup>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors> <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup> </PropertyGroup>
+46 -54
View File
@@ -18,11 +18,8 @@ using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Playlists;
using MediaBrowser.Controller.TV; using MediaBrowser.Controller.TV;
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Dto; using MediaBrowser.Model.Dto;
@@ -30,12 +27,7 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Querying; using MediaBrowser.Model.Querying;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Book = MediaBrowser.Controller.Entities.Book;
using Episode = MediaBrowser.Controller.Entities.TV.Episode;
using Genre = MediaBrowser.Controller.Entities.Genre; using Genre = MediaBrowser.Controller.Entities.Genre;
using Movie = MediaBrowser.Controller.Entities.Movies.Movie;
using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum;
using Series = MediaBrowser.Controller.Entities.TV.Series;
namespace Emby.Dlna.ContentDirectory namespace Emby.Dlna.ContentDirectory
{ {
@@ -539,7 +531,7 @@ namespace Emby.Dlna.ContentDirectory
User = user, User = user,
Recursive = true, Recursive = true,
IsMissing = false, IsMissing = false,
ExcludeItemTypes = new[] { nameof(Book) }, ExcludeItemTypes = new[] { BaseItemKind.Book },
IsFolder = isFolder, IsFolder = isFolder,
MediaTypes = mediaTypes, MediaTypes = mediaTypes,
DtoOptions = GetDtoOptions() DtoOptions = GetDtoOptions()
@@ -619,7 +611,7 @@ namespace Emby.Dlna.ContentDirectory
Limit = limit, Limit = limit,
StartIndex = startIndex, StartIndex = startIndex,
IsVirtualItem = false, IsVirtualItem = false,
ExcludeItemTypes = new[] { nameof(Book) }, ExcludeItemTypes = new[] { BaseItemKind.Book },
IsPlaceHolder = false, IsPlaceHolder = false,
DtoOptions = GetDtoOptions(), DtoOptions = GetDtoOptions(),
OrderBy = GetOrderBy(sort, folder.IsPreSorted) OrderBy = GetOrderBy(sort, folder.IsPreSorted)
@@ -644,7 +636,7 @@ namespace Emby.Dlna.ContentDirectory
{ {
StartIndex = startIndex, StartIndex = startIndex,
Limit = limit, Limit = limit,
IncludeItemTypes = new[] { nameof(LiveTvChannel) }, IncludeItemTypes = new[] { BaseItemKind.LiveTvChannel },
OrderBy = GetOrderBy(sort, false) OrderBy = GetOrderBy(sort, false)
}; };
@@ -675,39 +667,39 @@ namespace Emby.Dlna.ContentDirectory
switch (stubType) switch (stubType)
{ {
case StubType.Latest: case StubType.Latest:
return GetLatest(item, query, nameof(Audio)); return GetLatest(item, query, BaseItemKind.Audio);
case StubType.Playlists: case StubType.Playlists:
return GetMusicPlaylists(query); return GetMusicPlaylists(query);
case StubType.Albums: case StubType.Albums:
return GetChildrenOfItem(item, query, nameof(MusicAlbum)); return GetChildrenOfItem(item, query, BaseItemKind.MusicAlbum);
case StubType.Artists: case StubType.Artists:
return GetMusicArtists(item, query); return GetMusicArtists(item, query);
case StubType.AlbumArtists: case StubType.AlbumArtists:
return GetMusicAlbumArtists(item, query); return GetMusicAlbumArtists(item, query);
case StubType.FavoriteAlbums: case StubType.FavoriteAlbums:
return GetChildrenOfItem(item, query, nameof(MusicAlbum), true); return GetChildrenOfItem(item, query, BaseItemKind.MusicAlbum, true);
case StubType.FavoriteArtists: case StubType.FavoriteArtists:
return GetFavoriteArtists(item, query); return GetFavoriteArtists(item, query);
case StubType.FavoriteSongs: case StubType.FavoriteSongs:
return GetChildrenOfItem(item, query, nameof(Audio), true); return GetChildrenOfItem(item, query, BaseItemKind.Audio, true);
case StubType.Songs: case StubType.Songs:
return GetChildrenOfItem(item, query, nameof(Audio)); return GetChildrenOfItem(item, query, BaseItemKind.Audio);
case StubType.Genres: case StubType.Genres:
return GetMusicGenres(item, query); return GetMusicGenres(item, query);
} }
var serverItems = new ServerItem[] var serverItems = new ServerItem[]
{ {
new (item, StubType.Latest), new(item, StubType.Latest),
new (item, StubType.Playlists), new(item, StubType.Playlists),
new (item, StubType.Albums), new(item, StubType.Albums),
new (item, StubType.AlbumArtists), new(item, StubType.AlbumArtists),
new (item, StubType.Artists), new(item, StubType.Artists),
new (item, StubType.Songs), new(item, StubType.Songs),
new (item, StubType.Genres), new(item, StubType.Genres),
new (item, StubType.FavoriteArtists), new(item, StubType.FavoriteArtists),
new (item, StubType.FavoriteAlbums), new(item, StubType.FavoriteAlbums),
new (item, StubType.FavoriteSongs) new(item, StubType.FavoriteSongs)
}; };
if (limit < serverItems.Length) if (limit < serverItems.Length)
@@ -746,25 +738,25 @@ namespace Emby.Dlna.ContentDirectory
case StubType.ContinueWatching: case StubType.ContinueWatching:
return GetMovieContinueWatching(item, query); return GetMovieContinueWatching(item, query);
case StubType.Latest: case StubType.Latest:
return GetLatest(item, query, nameof(Movie)); return GetLatest(item, query, BaseItemKind.Movie);
case StubType.Movies: case StubType.Movies:
return GetChildrenOfItem(item, query, nameof(Movie)); return GetChildrenOfItem(item, query, BaseItemKind.Movie);
case StubType.Collections: case StubType.Collections:
return GetMovieCollections(query); return GetMovieCollections(query);
case StubType.Favorites: case StubType.Favorites:
return GetChildrenOfItem(item, query, nameof(Movie), true); return GetChildrenOfItem(item, query, BaseItemKind.Movie, true);
case StubType.Genres: case StubType.Genres:
return GetGenres(item, query); return GetGenres(item, query);
} }
var array = new ServerItem[] var array = new ServerItem[]
{ {
new (item, StubType.ContinueWatching), new(item, StubType.ContinueWatching),
new (item, StubType.Latest), new(item, StubType.Latest),
new (item, StubType.Movies), new(item, StubType.Movies),
new (item, StubType.Collections), new(item, StubType.Collections),
new (item, StubType.Favorites), new(item, StubType.Favorites),
new (item, StubType.Genres) new(item, StubType.Genres)
}; };
if (limit < array.Length) if (limit < array.Length)
@@ -831,26 +823,26 @@ namespace Emby.Dlna.ContentDirectory
case StubType.NextUp: case StubType.NextUp:
return GetNextUp(item, query); return GetNextUp(item, query);
case StubType.Latest: case StubType.Latest:
return GetLatest(item, query, nameof(Episode)); return GetLatest(item, query, BaseItemKind.Episode);
case StubType.Series: case StubType.Series:
return GetChildrenOfItem(item, query, nameof(Series)); return GetChildrenOfItem(item, query, BaseItemKind.Series);
case StubType.FavoriteSeries: case StubType.FavoriteSeries:
return GetChildrenOfItem(item, query, nameof(Series), true); return GetChildrenOfItem(item, query, BaseItemKind.Series, true);
case StubType.FavoriteEpisodes: case StubType.FavoriteEpisodes:
return GetChildrenOfItem(item, query, nameof(Episode), true); return GetChildrenOfItem(item, query, BaseItemKind.Episode, true);
case StubType.Genres: case StubType.Genres:
return GetGenres(item, query); return GetGenres(item, query);
} }
var serverItems = new ServerItem[] var serverItems = new ServerItem[]
{ {
new (item, StubType.ContinueWatching), new(item, StubType.ContinueWatching),
new (item, StubType.NextUp), new(item, StubType.NextUp),
new (item, StubType.Latest), new(item, StubType.Latest),
new (item, StubType.Series), new(item, StubType.Series),
new (item, StubType.FavoriteSeries), new(item, StubType.FavoriteSeries),
new (item, StubType.FavoriteEpisodes), new(item, StubType.FavoriteEpisodes),
new (item, StubType.Genres) new(item, StubType.Genres)
}; };
if (limit < serverItems.Length) if (limit < serverItems.Length)
@@ -898,7 +890,7 @@ namespace Emby.Dlna.ContentDirectory
private QueryResult<ServerItem> GetMovieCollections(InternalItemsQuery query) private QueryResult<ServerItem> GetMovieCollections(InternalItemsQuery query)
{ {
query.Recursive = true; query.Recursive = true;
query.IncludeItemTypes = new[] { nameof(BoxSet) }; query.IncludeItemTypes = new[] { BaseItemKind.BoxSet };
var result = _libraryManager.GetItemsResult(query); var result = _libraryManager.GetItemsResult(query);
@@ -913,7 +905,7 @@ namespace Emby.Dlna.ContentDirectory
/// <param name="itemType">The item type.</param> /// <param name="itemType">The item type.</param>
/// <param name="isFavorite">A value indicating whether to only fetch favorite items.</param> /// <param name="isFavorite">A value indicating whether to only fetch favorite items.</param>
/// <returns>The <see cref="QueryResult{ServerItem}"/>.</returns> /// <returns>The <see cref="QueryResult{ServerItem}"/>.</returns>
private QueryResult<ServerItem> GetChildrenOfItem(BaseItem parent, InternalItemsQuery query, string itemType, bool isFavorite = false) private QueryResult<ServerItem> GetChildrenOfItem(BaseItem parent, InternalItemsQuery query, BaseItemKind itemType, bool isFavorite = false)
{ {
query.Recursive = true; query.Recursive = true;
query.Parent = parent; query.Parent = parent;
@@ -1013,7 +1005,7 @@ namespace Emby.Dlna.ContentDirectory
private QueryResult<ServerItem> GetMusicPlaylists(InternalItemsQuery query) private QueryResult<ServerItem> GetMusicPlaylists(InternalItemsQuery query)
{ {
query.Parent = null; query.Parent = null;
query.IncludeItemTypes = new[] { nameof(Playlist) }; query.IncludeItemTypes = new[] { BaseItemKind.Playlist };
query.Recursive = true; query.Recursive = true;
var result = _libraryManager.GetItemsResult(query); var result = _libraryManager.GetItemsResult(query);
@@ -1052,7 +1044,7 @@ namespace Emby.Dlna.ContentDirectory
/// <param name="query">The <see cref="InternalItemsQuery"/>.</param> /// <param name="query">The <see cref="InternalItemsQuery"/>.</param>
/// <param name="itemType">The item type.</param> /// <param name="itemType">The item type.</param>
/// <returns>The <see cref="QueryResult{ServerItem}"/>.</returns> /// <returns>The <see cref="QueryResult{ServerItem}"/>.</returns>
private QueryResult<ServerItem> GetLatest(BaseItem parent, InternalItemsQuery query, string itemType) private QueryResult<ServerItem> GetLatest(BaseItem parent, InternalItemsQuery query, BaseItemKind itemType)
{ {
query.OrderBy = Array.Empty<(string, SortOrder)>(); query.OrderBy = Array.Empty<(string, SortOrder)>();
@@ -1086,7 +1078,7 @@ namespace Emby.Dlna.ContentDirectory
{ {
Recursive = true, Recursive = true,
ArtistIds = new[] { item.Id }, ArtistIds = new[] { item.Id },
IncludeItemTypes = new[] { nameof(MusicAlbum) }, IncludeItemTypes = new[] { BaseItemKind.MusicAlbum },
Limit = limit, Limit = limit,
StartIndex = startIndex, StartIndex = startIndex,
DtoOptions = GetDtoOptions(), DtoOptions = GetDtoOptions(),
@@ -1115,8 +1107,8 @@ namespace Emby.Dlna.ContentDirectory
GenreIds = new[] { item.Id }, GenreIds = new[] { item.Id },
IncludeItemTypes = new[] IncludeItemTypes = new[]
{ {
nameof(Movie), BaseItemKind.Movie,
nameof(Series) BaseItemKind.Series
}, },
Limit = limit, Limit = limit,
StartIndex = startIndex, StartIndex = startIndex,
@@ -1144,7 +1136,7 @@ namespace Emby.Dlna.ContentDirectory
{ {
Recursive = true, Recursive = true,
GenreIds = new[] { item.Id }, GenreIds = new[] { item.Id },
IncludeItemTypes = new[] { nameof(MusicAlbum) }, IncludeItemTypes = new[] { BaseItemKind.MusicAlbum },
Limit = limit, Limit = limit,
StartIndex = startIndex, StartIndex = startIndex,
DtoOptions = GetDtoOptions(), DtoOptions = GetDtoOptions(),
+1 -2
View File
@@ -17,8 +17,7 @@ namespace Emby.Dlna.Didl
public Filter(string filter) public Filter(string filter)
{ {
_all = string.Equals(filter, "*", StringComparison.OrdinalIgnoreCase); _all = string.Equals(filter, "*", StringComparison.OrdinalIgnoreCase);
_fields = filter.Split(',', StringSplitOptions.RemoveEmptyEntries);
_fields = (filter ?? string.Empty).Split(',', StringSplitOptions.RemoveEmptyEntries);
} }
public bool Contains(string field) public bool Contains(string field)
+4 -13
View File
@@ -5,7 +5,6 @@ using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Text;
using System.Text.Json; using System.Text.Json;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -84,8 +83,7 @@ namespace Emby.Dlna
{ {
lock (_profiles) lock (_profiles)
{ {
var list = _profiles.Values.ToList(); return _profiles.Values
return list
.OrderBy(i => i.Item1.Info.Type == DeviceProfileType.User ? 0 : 1) .OrderBy(i => i.Item1.Info.Type == DeviceProfileType.User ? 0 : 1)
.ThenBy(i => i.Item1.Info.Name) .ThenBy(i => i.Item1.Info.Name)
.Select(i => i.Item2) .Select(i => i.Item2)
@@ -227,11 +225,8 @@ namespace Emby.Dlna
{ {
try try
{ {
var xmlFies = _fileSystem.GetFilePaths(path) return _fileSystem.GetFilePaths(path)
.Where(i => string.Equals(Path.GetExtension(i), ".xml", StringComparison.OrdinalIgnoreCase)) .Where(i => string.Equals(Path.GetExtension(i), ".xml", StringComparison.OrdinalIgnoreCase))
.ToList();
return xmlFies
.Select(i => ParseProfileFile(i, type)) .Select(i => ParseProfileFile(i, type))
.Where(i => i != null) .Where(i => i != null)
.ToList()!; // We just filtered out all the nulls .ToList()!; // We just filtered out all the nulls
@@ -253,11 +248,8 @@ namespace Emby.Dlna
try try
{ {
DeviceProfile profile;
var tempProfile = (DeviceProfile)_xmlSerializer.DeserializeFromFile(typeof(DeviceProfile), path); var tempProfile = (DeviceProfile)_xmlSerializer.DeserializeFromFile(typeof(DeviceProfile), path);
var profile = ReserializeProfile(tempProfile);
profile = ReserializeProfile(tempProfile);
profile.Id = path.ToLowerInvariant().GetMD5().ToString("N", CultureInfo.InvariantCulture); profile.Id = path.ToLowerInvariant().GetMD5().ToString("N", CultureInfo.InvariantCulture);
@@ -296,8 +288,7 @@ namespace Emby.Dlna
{ {
lock (_profiles) lock (_profiles)
{ {
var list = _profiles.Values.ToList(); return _profiles.Values
return list
.Select(i => i.Item1) .Select(i => i.Item1)
.OrderBy(i => i.Info.Type == DeviceProfileType.User ? 0 : 1) .OrderBy(i => i.Info.Type == DeviceProfileType.User ? 0 : 1)
.ThenBy(i => i.Info.Name); .ThenBy(i => i.Info.Name);
+5 -1
View File
@@ -22,10 +22,14 @@
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
</PropertyGroup>
<!-- Code Analyzers--> <!-- Code Analyzers-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' "> <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" /> <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup> </ItemGroup>
+26 -33
View File
@@ -1179,6 +1179,7 @@ namespace Emby.Dlna.PlayTo
return new Device(deviceProperties, httpClientFactory, logger); return new Device(deviceProperties, httpClientFactory, logger);
} }
#nullable enable
private static DeviceIcon CreateIcon(XElement element) private static DeviceIcon CreateIcon(XElement element)
{ {
if (element == null) if (element == null)
@@ -1186,69 +1187,61 @@ namespace Emby.Dlna.PlayTo
throw new ArgumentNullException(nameof(element)); throw new ArgumentNullException(nameof(element));
} }
var mimeType = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("mimetype"));
var width = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("width")); var width = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("width"));
var height = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("height")); var height = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("height"));
var depth = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("depth"));
var url = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("url"));
var widthValue = int.Parse(width, NumberStyles.Integer, CultureInfo.InvariantCulture); _ = int.TryParse(width, NumberStyles.Integer, CultureInfo.InvariantCulture, out var widthValue);
var heightValue = int.Parse(height, NumberStyles.Integer, CultureInfo.InvariantCulture); _ = int.TryParse(height, NumberStyles.Integer, CultureInfo.InvariantCulture, out var heightValue);
return new DeviceIcon return new DeviceIcon
{ {
Depth = depth, Depth = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("depth")) ?? string.Empty,
Height = heightValue, Height = heightValue,
MimeType = mimeType, MimeType = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("mimetype")) ?? string.Empty,
Url = url, Url = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("url")) ?? string.Empty,
Width = widthValue Width = widthValue
}; };
} }
private static DeviceService Create(XElement element) private static DeviceService Create(XElement element)
{ => new DeviceService()
var type = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("serviceType"));
var id = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("serviceId"));
var scpdUrl = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("SCPDURL"));
var controlURL = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("controlURL"));
var eventSubURL = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("eventSubURL"));
return new DeviceService
{ {
ControlUrl = controlURL, ControlUrl = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("controlURL")) ?? string.Empty,
EventSubUrl = eventSubURL, EventSubUrl = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("eventSubURL")) ?? string.Empty,
ScpdUrl = scpdUrl, ScpdUrl = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("SCPDURL")) ?? string.Empty,
ServiceId = id, ServiceId = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("serviceId")) ?? string.Empty,
ServiceType = type ServiceType = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("serviceType")) ?? string.Empty
}; };
}
private void UpdateMediaInfo(UBaseObject mediaInfo, TransportState state) private void UpdateMediaInfo(UBaseObject? mediaInfo, TransportState state)
{ {
TransportState = state; TransportState = state;
var previousMediaInfo = CurrentMediaInfo; var previousMediaInfo = CurrentMediaInfo;
CurrentMediaInfo = mediaInfo; CurrentMediaInfo = mediaInfo;
if (previousMediaInfo == null && mediaInfo != null) if (mediaInfo == null)
{
if (previousMediaInfo != null)
{
OnPlaybackStop(previousMediaInfo);
}
}
else if (previousMediaInfo == null)
{ {
if (state != TransportState.Stopped) if (state != TransportState.Stopped)
{ {
OnPlaybackStart(mediaInfo); OnPlaybackStart(mediaInfo);
} }
} }
else if (mediaInfo != null && previousMediaInfo != null && !mediaInfo.Equals(previousMediaInfo)) else if (mediaInfo.Equals(previousMediaInfo))
{
OnMediaChanged(previousMediaInfo, mediaInfo);
}
else if (mediaInfo == null && previousMediaInfo != null)
{
OnPlaybackStop(previousMediaInfo);
}
else if (mediaInfo != null && mediaInfo.Equals(previousMediaInfo))
{ {
OnPlaybackProgress(mediaInfo); OnPlaybackProgress(mediaInfo);
} }
else
{
OnMediaChanged(previousMediaInfo, mediaInfo);
}
} }
private void OnPlaybackStart(UBaseObject mediaInfo) private void OnPlaybackStart(UBaseObject mediaInfo)
+3 -3
View File
@@ -210,9 +210,9 @@ namespace Emby.Dlna.PlayTo
var mediaSource = await streamInfo.GetMediaSource(CancellationToken.None).ConfigureAwait(false); var mediaSource = await streamInfo.GetMediaSource(CancellationToken.None).ConfigureAwait(false);
var duration = mediaSource == null ? var duration = mediaSource == null
(_device.Duration == null ? (long?)null : _device.Duration.Value.Ticks) : ? _device.Duration?.Ticks
mediaSource.RunTimeTicks; : mediaSource.RunTimeTicks;
var playedToCompletion = positionTicks.HasValue && positionTicks.Value == 0; var playedToCompletion = positionTicks.HasValue && positionTicks.Value == 0;
+1 -1
View File
@@ -175,7 +175,7 @@ namespace Emby.Dlna.PlayTo
var sendValue = state.AllowedValues.FirstOrDefault(a => string.Equals(a, commandParameter, StringComparison.OrdinalIgnoreCase)) ?? var sendValue = state.AllowedValues.FirstOrDefault(a => string.Equals(a, commandParameter, StringComparison.OrdinalIgnoreCase)) ??
(state.AllowedValues.Count > 0 ? state.AllowedValues[0] : value); (state.AllowedValues.Count > 0 ? state.AllowedValues[0] : value);
return string.Format(CultureInfo.InvariantCulture, "<{0} xmlns:dt=\"urn:schemas-microsoft-com:datatypes\" dt:dt=\"{1}\">{2}</{0}>", argument.Name, state.DataType ?? "string", sendValue); return string.Format(CultureInfo.InvariantCulture, "<{0} xmlns:dt=\"urn:schemas-microsoft-com:datatypes\" dt:dt=\"{1}\">{2}</{0}>", argument.Name, state.DataType, sendValue);
} }
return string.Format(CultureInfo.InvariantCulture, "<{0}>{1}</{0}>", argument.Name, value); return string.Format(CultureInfo.InvariantCulture, "<{0}>{1}</{0}>", argument.Name, value);
+1 -2
View File
@@ -167,8 +167,7 @@ namespace Emby.Dlna.Profiles
public void AddXmlRootAttribute(string name, string value) public void AddXmlRootAttribute(string name, string value)
{ {
var atts = XmlRootAttributes ?? System.Array.Empty<XmlAttribute>(); var list = XmlRootAttributes.ToList();
var list = atts.ToList();
list.Add(new XmlAttribute list.Add(new XmlAttribute
{ {
+4 -4
View File
@@ -189,7 +189,7 @@ namespace Emby.Dlna.Server
builder.Append("<icon>"); builder.Append("<icon>");
builder.Append("<mimetype>") builder.Append("<mimetype>")
.Append(SecurityElement.Escape(icon.MimeType ?? string.Empty)) .Append(SecurityElement.Escape(icon.MimeType))
.Append("</mimetype>"); .Append("</mimetype>");
builder.Append("<width>") builder.Append("<width>")
.Append(SecurityElement.Escape(icon.Width.ToString(CultureInfo.InvariantCulture))) .Append(SecurityElement.Escape(icon.Width.ToString(CultureInfo.InvariantCulture)))
@@ -198,7 +198,7 @@ namespace Emby.Dlna.Server
.Append(SecurityElement.Escape(icon.Height.ToString(CultureInfo.InvariantCulture))) .Append(SecurityElement.Escape(icon.Height.ToString(CultureInfo.InvariantCulture)))
.Append("</height>"); .Append("</height>");
builder.Append("<depth>") builder.Append("<depth>")
.Append(SecurityElement.Escape(icon.Depth ?? string.Empty)) .Append(SecurityElement.Escape(icon.Depth))
.Append("</depth>"); .Append("</depth>");
builder.Append("<url>") builder.Append("<url>")
.Append(BuildUrl(icon.Url)) .Append(BuildUrl(icon.Url))
@@ -219,10 +219,10 @@ namespace Emby.Dlna.Server
builder.Append("<service>"); builder.Append("<service>");
builder.Append("<serviceType>") builder.Append("<serviceType>")
.Append(SecurityElement.Escape(service.ServiceType ?? string.Empty)) .Append(SecurityElement.Escape(service.ServiceType))
.Append("</serviceType>"); .Append("</serviceType>");
builder.Append("<serviceId>") builder.Append("<serviceId>")
.Append(SecurityElement.Escape(service.ServiceId ?? string.Empty)) .Append(SecurityElement.Escape(service.ServiceId))
.Append("</serviceId>"); .Append("</serviceId>");
builder.Append("<SCPDURL>") builder.Append("<SCPDURL>")
.Append(BuildUrl(service.ScpdUrl)) .Append(BuildUrl(service.ScpdUrl))
+6 -6
View File
@@ -38,7 +38,7 @@ namespace Emby.Dlna.Service
builder.Append("<action>"); builder.Append("<action>");
builder.Append("<name>") builder.Append("<name>")
.Append(SecurityElement.Escape(item.Name ?? string.Empty)) .Append(SecurityElement.Escape(item.Name))
.Append("</name>"); .Append("</name>");
builder.Append("<argumentList>"); builder.Append("<argumentList>");
@@ -48,13 +48,13 @@ namespace Emby.Dlna.Service
builder.Append("<argument>"); builder.Append("<argument>");
builder.Append("<name>") builder.Append("<name>")
.Append(SecurityElement.Escape(argument.Name ?? string.Empty)) .Append(SecurityElement.Escape(argument.Name))
.Append("</name>"); .Append("</name>");
builder.Append("<direction>") builder.Append("<direction>")
.Append(SecurityElement.Escape(argument.Direction ?? string.Empty)) .Append(SecurityElement.Escape(argument.Direction))
.Append("</direction>"); .Append("</direction>");
builder.Append("<relatedStateVariable>") builder.Append("<relatedStateVariable>")
.Append(SecurityElement.Escape(argument.RelatedStateVariable ?? string.Empty)) .Append(SecurityElement.Escape(argument.RelatedStateVariable))
.Append("</relatedStateVariable>"); .Append("</relatedStateVariable>");
builder.Append("</argument>"); builder.Append("</argument>");
@@ -81,10 +81,10 @@ namespace Emby.Dlna.Service
.Append("\">"); .Append("\">");
builder.Append("<name>") builder.Append("<name>")
.Append(SecurityElement.Escape(item.Name ?? string.Empty)) .Append(SecurityElement.Escape(item.Name))
.Append("</name>"); .Append("</name>");
builder.Append("<dataType>") builder.Append("<dataType>")
.Append(SecurityElement.Escape(item.DataType ?? string.Empty)) .Append(SecurityElement.Escape(item.DataType))
.Append("</dataType>"); .Append("</dataType>");
if (item.AllowedValues.Count > 0) if (item.AllowedValues.Count > 0)
+5 -1
View File
@@ -11,6 +11,10 @@
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" /> <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
<ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" /> <ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" />
@@ -24,7 +28,7 @@
<!-- Code analysers--> <!-- Code analysers-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' "> <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" /> <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup> </ItemGroup>
+12 -10
View File
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net.Mime;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Data.Entities; using Jellyfin.Data.Entities;
@@ -101,8 +102,7 @@ namespace Emby.Drawing
public async Task ProcessImage(ImageProcessingOptions options, Stream toStream) public async Task ProcessImage(ImageProcessingOptions options, Stream toStream)
{ {
var file = await ProcessImage(options).ConfigureAwait(false); var file = await ProcessImage(options).ConfigureAwait(false);
using (var fileStream = AsyncFile.OpenRead(file.Path))
using (var fileStream = AsyncFile.OpenRead(file.Item1))
{ {
await fileStream.CopyToAsync(toStream).ConfigureAwait(false); await fileStream.CopyToAsync(toStream).ConfigureAwait(false);
} }
@@ -117,7 +117,7 @@ namespace Emby.Drawing
=> _transparentImageTypes.Contains(Path.GetExtension(path)); => _transparentImageTypes.Contains(Path.GetExtension(path));
/// <inheritdoc /> /// <inheritdoc />
public async Task<(string path, string? mimeType, DateTime dateModified)> ProcessImage(ImageProcessingOptions options) public async Task<(string Path, string? MimeType, DateTime DateModified)> ProcessImage(ImageProcessingOptions options)
{ {
ItemImageInfo originalImage = options.Image; ItemImageInfo originalImage = options.Image;
BaseItem item = options.Item; BaseItem item = options.Item;
@@ -130,20 +130,22 @@ namespace Emby.Drawing
originalImageSize = new ImageDimensions(originalImage.Width, originalImage.Height); originalImageSize = new ImageDimensions(originalImage.Width, originalImage.Height);
} }
var mimeType = MimeTypes.GetMimeType(originalImagePath);
if (!_imageEncoder.SupportsImageEncoding) if (!_imageEncoder.SupportsImageEncoding)
{ {
return (originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified); return (originalImagePath, mimeType, dateModified);
} }
var supportedImageInfo = await GetSupportedImage(originalImagePath, dateModified).ConfigureAwait(false); var supportedImageInfo = await GetSupportedImage(originalImagePath, dateModified).ConfigureAwait(false);
originalImagePath = supportedImageInfo.path; originalImagePath = supportedImageInfo.Path;
if (!File.Exists(originalImagePath)) // Original file doesn't exist, or original file is gif.
if (!File.Exists(originalImagePath) || string.Equals(mimeType, MediaTypeNames.Image.Gif, StringComparison.OrdinalIgnoreCase))
{ {
return (originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified); return (originalImagePath, mimeType, dateModified);
} }
dateModified = supportedImageInfo.dateModified; dateModified = supportedImageInfo.DateModified;
bool requiresTransparency = _transparentImageTypes.Contains(Path.GetExtension(originalImagePath)); bool requiresTransparency = _transparentImageTypes.Contains(Path.GetExtension(originalImagePath));
bool autoOrient = false; bool autoOrient = false;
@@ -243,7 +245,7 @@ namespace Emby.Drawing
return ImageFormat.Jpg; return ImageFormat.Jpg;
} }
private string? GetMimeType(ImageFormat format, string path) private string GetMimeType(ImageFormat format, string path)
=> format switch => format switch
{ {
ImageFormat.Bmp => MimeTypes.GetMimeType("i.bmp"), ImageFormat.Bmp => MimeTypes.GetMimeType("i.bmp"),
@@ -437,7 +439,7 @@ namespace Emby.Drawing
.ToString("N", CultureInfo.InvariantCulture); .ToString("N", CultureInfo.InvariantCulture);
} }
private async Task<(string path, DateTime dateModified)> GetSupportedImage(string originalImagePath, DateTime dateModified) private async Task<(string Path, DateTime DateModified)> GetSupportedImage(string originalImagePath, DateTime dateModified)
{ {
var inputFormat = Path.GetExtension(originalImagePath) var inputFormat = Path.GetExtension(originalImagePath)
.TrimStart('.') .TrimStart('.')
@@ -14,6 +14,7 @@ namespace Emby.Naming.AudioBook
public class AudioBookListResolver public class AudioBookListResolver
{ {
private readonly NamingOptions _options; private readonly NamingOptions _options;
private readonly AudioBookResolver _audioBookResolver;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="AudioBookListResolver"/> class. /// Initializes a new instance of the <see cref="AudioBookListResolver"/> class.
@@ -22,6 +23,7 @@ namespace Emby.Naming.AudioBook
public AudioBookListResolver(NamingOptions options) public AudioBookListResolver(NamingOptions options)
{ {
_options = options; _options = options;
_audioBookResolver = new AudioBookResolver(_options);
} }
/// <summary> /// <summary>
@@ -31,21 +33,18 @@ namespace Emby.Naming.AudioBook
/// <returns>Returns IEnumerable of <see cref="AudioBookInfo"/>.</returns> /// <returns>Returns IEnumerable of <see cref="AudioBookInfo"/>.</returns>
public IEnumerable<AudioBookInfo> Resolve(IEnumerable<FileSystemMetadata> files) public IEnumerable<AudioBookInfo> Resolve(IEnumerable<FileSystemMetadata> files)
{ {
var audioBookResolver = new AudioBookResolver(_options);
// File with empty fullname will be sorted out here. // File with empty fullname will be sorted out here.
var audiobookFileInfos = files var audiobookFileInfos = files
.Select(i => audioBookResolver.Resolve(i.FullName)) .Select(i => _audioBookResolver.Resolve(i.FullName))
.OfType<AudioBookFileInfo>() .OfType<AudioBookFileInfo>()
.ToList(); .ToList();
var stackResult = new StackResolver(_options) var stackResult = StackResolver.ResolveAudioBooks(audiobookFileInfos);
.ResolveAudioBooks(audiobookFileInfos);
foreach (var stack in stackResult) foreach (var stack in stackResult)
{ {
var stackFiles = stack.Files var stackFiles = stack.Files
.Select(i => audioBookResolver.Resolve(i)) .Select(i => _audioBookResolver.Resolve(i))
.OfType<AudioBookFileInfo>() .OfType<AudioBookFileInfo>()
.ToList(); .ToList();
+2 -2
View File
@@ -1,7 +1,7 @@
using System; using System;
using System.IO; using System.IO;
using System.Linq;
using Emby.Naming.Common; using Emby.Naming.Common;
using Jellyfin.Extensions;
namespace Emby.Naming.AudioBook namespace Emby.Naming.AudioBook
{ {
@@ -37,7 +37,7 @@ namespace Emby.Naming.AudioBook
var extension = Path.GetExtension(path); var extension = Path.GetExtension(path);
// Check supported extensions // Check supported extensions
if (!_options.AudioFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)) if (!_options.AudioFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase))
{ {
return null; return null;
} }
+46 -13
View File
@@ -1,6 +1,7 @@
#pragma warning disable CA1819 #pragma warning disable CA1819
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Emby.Naming.Video; using Emby.Naming.Video;
@@ -124,11 +125,11 @@ namespace Emby.Naming.Common
token: "DSR") token: "DSR")
}; };
VideoFileStackingExpressions = new[] VideoFileStackingRules = new[]
{ {
"(?<title>.*?)(?<volume>[ _.-]*(?:cd|dvd|p(?:ar)?t|dis[ck])[ _.-]*[0-9]+)(?<ignore>.*?)(?<extension>\\.[^.]+)$", new FileStackRule(@"^(?<filename>.*?)(?:(?<=[\]\)\}])|[ _.-]+)[\(\[]?(?<parttype>cd|dvd|part|pt|dis[ck])[ _.-]*(?<number>[0-9]+)[\)\]]?(?:\.[^.]+)?$", true),
"(?<title>.*?)(?<volume>[ _.-]*(?:cd|dvd|p(?:ar)?t|dis[ck])[ _.-]*[a-d])(?<ignore>.*?)(?<extension>\\.[^.]+)$", new FileStackRule(@"^(?<filename>.*?)(?:(?<=[\]\)\}])|[ _.-]+)[\(\[]?(?<parttype>cd|dvd|part|pt|dis[ck])[ _.-]*(?<number>[a-d])[\)\]]?(?:\.[^.]+)?$", false),
"(?<title>.*?)(?<volume>[ ._-]*[a-d])(?<ignore>.*?)(?<extension>\\.[^.]+)$" new FileStackRule(@"^(?<filename>.*?)(?:(?<=[\]\)\}])|[ _.-]?)(?<number>[a-d])(?:\.[^.]+)?$", false)
}; };
CleanDateTimes = new[] CleanDateTimes = new[]
@@ -403,6 +404,12 @@ namespace Emby.Naming.Common
VideoExtraRules = new[] VideoExtraRules = new[]
{ {
new ExtraRule(
ExtraType.Trailer,
ExtraRuleType.DirectoryName,
"trailers",
MediaType.Video),
new ExtraRule( new ExtraRule(
ExtraType.Trailer, ExtraType.Trailer,
ExtraRuleType.Filename, ExtraRuleType.Filename,
@@ -463,12 +470,24 @@ namespace Emby.Naming.Common
" sample", " sample",
MediaType.Video), MediaType.Video),
new ExtraRule(
ExtraType.ThemeVideo,
ExtraRuleType.DirectoryName,
"backdrops",
MediaType.Video),
new ExtraRule( new ExtraRule(
ExtraType.ThemeSong, ExtraType.ThemeSong,
ExtraRuleType.Filename, ExtraRuleType.Filename,
"theme", "theme",
MediaType.Audio), MediaType.Audio),
new ExtraRule(
ExtraType.ThemeSong,
ExtraRuleType.DirectoryName,
"theme-music",
MediaType.Audio),
new ExtraRule( new ExtraRule(
ExtraType.Scene, ExtraType.Scene,
ExtraRuleType.Suffix, ExtraRuleType.Suffix,
@@ -563,7 +582,7 @@ namespace Emby.Naming.Common
ExtraType.Unknown, ExtraType.Unknown,
ExtraRuleType.DirectoryName, ExtraRuleType.DirectoryName,
"extras", "extras",
MediaType.Video), MediaType.Video)
}; };
Format3DRules = new[] Format3DRules = new[]
@@ -675,9 +694,29 @@ namespace Emby.Naming.Common
.Distinct(StringComparer.OrdinalIgnoreCase) .Distinct(StringComparer.OrdinalIgnoreCase)
.ToArray(); .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(); Compile();
} }
/// <summary>
/// Gets or sets the folder name to extra types mapping.
/// </summary>
public Dictionary<string, ExtraType> AllExtrasTypesFolderNames { get; set; }
/// <summary> /// <summary>
/// Gets or sets list of audio file extensions. /// Gets or sets list of audio file extensions.
/// </summary> /// </summary>
@@ -759,9 +798,9 @@ namespace Emby.Naming.Common
public Format3DRule[] Format3DRules { get; set; } public Format3DRule[] Format3DRules { get; set; }
/// <summary> /// <summary>
/// Gets or sets list of raw video file-stacking expressions strings. /// Gets the file stacking rules.
/// </summary> /// </summary>
public string[] VideoFileStackingExpressions { get; set; } public FileStackRule[] VideoFileStackingRules { get; }
/// <summary> /// <summary>
/// Gets or sets list of raw clean DateTimes regular expressions strings. /// Gets or sets list of raw clean DateTimes regular expressions strings.
@@ -783,11 +822,6 @@ namespace Emby.Naming.Common
/// </summary> /// </summary>
public ExtraRule[] VideoExtraRules { get; set; } public ExtraRule[] VideoExtraRules { get; set; }
/// <summary>
/// Gets list of video file-stack regular expressions.
/// </summary>
public Regex[] VideoFileStackingRegexes { get; private set; } = Array.Empty<Regex>();
/// <summary> /// <summary>
/// Gets list of clean datetime regular expressions. /// Gets list of clean datetime regular expressions.
/// </summary> /// </summary>
@@ -813,7 +847,6 @@ namespace Emby.Naming.Common
/// </summary> /// </summary>
public void Compile() public void Compile()
{ {
VideoFileStackingRegexes = VideoFileStackingExpressions.Select(Compile).ToArray();
CleanDateTimeRegexes = CleanDateTimes.Select(Compile).ToArray(); CleanDateTimeRegexes = CleanDateTimes.Select(Compile).ToArray();
CleanStringRegexes = CleanStrings.Select(Compile).ToArray(); CleanStringRegexes = CleanStrings.Select(Compile).ToArray();
EpisodeWithoutSeasonRegexes = EpisodeWithoutSeasonExpressions.Select(Compile).ToArray(); EpisodeWithoutSeasonRegexes = EpisodeWithoutSeasonExpressions.Select(Compile).ToArray();
+5 -1
View File
@@ -15,6 +15,10 @@
<SymbolPackageFormat>snupkg</SymbolPackageFormat> <SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
</PropertyGroup>
<PropertyGroup Condition=" '$(Stability)'=='Unstable'"> <PropertyGroup Condition=" '$(Stability)'=='Unstable'">
<!-- Include all symbols in the main nupkg until Azure Artifact Feed starts supporting ingesting NuGet symbol packages. --> <!-- Include all symbols in the main nupkg until Azure Artifact Feed starts supporting ingesting NuGet symbol packages. -->
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder> <AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
@@ -44,7 +48,7 @@
<!-- Code Analyzers--> <!-- Code Analyzers-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' "> <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" /> <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup> </ItemGroup>
+6 -5
View File
@@ -2,6 +2,7 @@ using System;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Emby.Naming.Common; using Emby.Naming.Common;
using Jellyfin.Extensions;
namespace Emby.Naming.Subtitles namespace Emby.Naming.Subtitles
{ {
@@ -34,7 +35,7 @@ namespace Emby.Naming.Subtitles
} }
var extension = Path.GetExtension(path); var extension = Path.GetExtension(path);
if (!_options.SubtitleFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)) if (!_options.SubtitleFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase))
{ {
return null; return null;
} }
@@ -42,11 +43,11 @@ namespace Emby.Naming.Subtitles
var flags = GetFlags(path); var flags = GetFlags(path);
var info = new SubtitleInfo( var info = new SubtitleInfo(
path, path,
_options.SubtitleDefaultFlags.Any(i => flags.Contains(i, StringComparer.OrdinalIgnoreCase)), _options.SubtitleDefaultFlags.Any(i => flags.Contains(i, StringComparison.OrdinalIgnoreCase)),
_options.SubtitleForcedFlags.Any(i => flags.Contains(i, StringComparer.OrdinalIgnoreCase))); _options.SubtitleForcedFlags.Any(i => flags.Contains(i, StringComparison.OrdinalIgnoreCase)));
var parts = flags.Where(i => !_options.SubtitleDefaultFlags.Contains(i, StringComparer.OrdinalIgnoreCase) var parts = flags.Where(i => !_options.SubtitleDefaultFlags.Contains(i, StringComparison.OrdinalIgnoreCase)
&& !_options.SubtitleForcedFlags.Contains(i, StringComparer.OrdinalIgnoreCase)) && !_options.SubtitleForcedFlags.Contains(i, StringComparison.OrdinalIgnoreCase))
.ToList(); .ToList();
// Should have a name, language and file extension // Should have a name, language and file extension
+2 -2
View File
@@ -1,8 +1,8 @@
using System; using System;
using System.IO; using System.IO;
using System.Linq;
using Emby.Naming.Common; using Emby.Naming.Common;
using Emby.Naming.Video; using Emby.Naming.Video;
using Jellyfin.Extensions;
namespace Emby.Naming.TV namespace Emby.Naming.TV
{ {
@@ -48,7 +48,7 @@ namespace Emby.Naming.TV
{ {
var extension = Path.GetExtension(path); var extension = Path.GetExtension(path);
// Check supported extensions // Check supported extensions
if (!_options.VideoFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)) if (!_options.VideoFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase))
{ {
// It's not supported. Check stub extensions // It's not supported. Check stub extensions
if (!StubResolver.TryResolveFile(path, _options, out stubType)) if (!StubResolver.TryResolveFile(path, _options, out stubType))
+3 -3
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="supportSpecialAliases">if set to <c>true</c> [support special aliases].</param>
/// <param name="supportNumericSeasonFolders">if set to <c>true</c> [support numeric season folders].</param> /// <param name="supportNumericSeasonFolders">if set to <c>true</c> [support numeric season folders].</param>
/// <returns>System.Nullable{System.Int32}.</returns> /// <returns>System.Nullable{System.Int32}.</returns>
private static (int? seasonNumber, bool isSeasonFolder) GetSeasonNumberFromPath( private static (int? SeasonNumber, bool IsSeasonFolder) GetSeasonNumberFromPath(
string path, string path,
bool supportSpecialAliases, bool supportSpecialAliases,
bool supportNumericSeasonFolders) bool supportNumericSeasonFolders)
@@ -99,7 +99,7 @@ namespace Emby.Naming.TV
if (filename.Contains(name, StringComparison.OrdinalIgnoreCase)) if (filename.Contains(name, StringComparison.OrdinalIgnoreCase))
{ {
var result = GetSeasonNumberFromPathSubstring(filename.Replace(name, " ", StringComparison.OrdinalIgnoreCase)); var result = GetSeasonNumberFromPathSubstring(filename.Replace(name, " ", StringComparison.OrdinalIgnoreCase));
if (result.seasonNumber.HasValue) if (result.SeasonNumber.HasValue)
{ {
return result; return result;
} }
@@ -142,7 +142,7 @@ namespace Emby.Naming.TV
/// </summary> /// </summary>
/// <param name="path">The path.</param> /// <param name="path">The path.</param>
/// <returns>System.Nullable{System.Int32}.</returns> /// <returns>System.Nullable{System.Int32}.</returns>
private static (int? seasonNumber, bool isSeasonFolder) GetSeasonNumberFromPathSubstring(ReadOnlySpan<char> path) private static (int? SeasonNumber, bool IsSeasonFolder) GetSeasonNumberFromPathSubstring(ReadOnlySpan<char> path)
{ {
var numericStart = -1; var numericStart = -1;
var length = 0; var length = 0;
+1 -2
View File
@@ -1,4 +1,3 @@
using System.Globalization;
using Emby.Naming.Common; using Emby.Naming.Common;
namespace Emby.Naming.TV namespace Emby.Naming.TV
@@ -51,7 +50,7 @@ namespace Emby.Naming.TV
if (expression.IsNamed) if (expression.IsNamed)
{ {
result.SeriesName = match.Groups["seriesname"].Value; result.SeriesName = match.Groups["seriesname"].Value;
result.Success = !string.IsNullOrEmpty(result.SeriesName) && !string.IsNullOrEmpty(match.Groups["seasonnumber"]?.Value); result.Success = !string.IsNullOrEmpty(result.SeriesName) && !match.Groups["seasonnumber"].ValueSpan.IsEmpty;
} }
} }
-1
View File
@@ -1,4 +1,3 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
@@ -9,45 +9,27 @@ namespace Emby.Naming.Video
/// <summary> /// <summary>
/// Resolve if file is extra for video. /// Resolve if file is extra for video.
/// </summary> /// </summary>
public class ExtraResolver public static class ExtraRuleResolver
{ {
private static readonly char[] _digits = new[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' }; private static readonly char[] _digits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };
private readonly NamingOptions _options;
/// <summary>
/// Initializes a new instance of the <see cref="ExtraResolver"/> class.
/// </summary>
/// <param name="options"><see cref="NamingOptions"/> object containing VideoExtraRules and passed to <see cref="AudioFileParser"/> and <see cref="VideoResolver"/>.</param>
public ExtraResolver(NamingOptions options)
{
_options = options;
}
/// <summary> /// <summary>
/// Attempts to resolve if file is extra. /// Attempts to resolve if file is extra.
/// </summary> /// </summary>
/// <param name="path">Path to file.</param> /// <param name="path">Path to file.</param>
/// <param name="namingOptions">The naming options.</param>
/// <returns>Returns <see cref="ExtraResult"/> object.</returns> /// <returns>Returns <see cref="ExtraResult"/> object.</returns>
public ExtraResult GetExtraInfo(string path) public static ExtraResult GetExtraInfo(string path, NamingOptions namingOptions)
{ {
var result = new ExtraResult(); var result = new ExtraResult();
for (var i = 0; i < _options.VideoExtraRules.Length; i++) for (var i = 0; i < namingOptions.VideoExtraRules.Length; i++)
{ {
var rule = _options.VideoExtraRules[i]; var rule = namingOptions.VideoExtraRules[i];
if (rule.MediaType == MediaType.Audio) if ((rule.MediaType == MediaType.Audio && !AudioFileParser.IsAudioFile(path, namingOptions))
|| (rule.MediaType == MediaType.Video && !VideoResolver.IsVideoFile(path, namingOptions)))
{ {
if (!AudioFileParser.IsAudioFile(path, _options)) continue;
{
continue;
}
}
else if (rule.MediaType == MediaType.Video)
{
if (!VideoResolver.IsVideoFile(path, _options))
{
continue;
}
} }
var pathSpan = path.AsSpan(); var pathSpan = path.AsSpan();
@@ -76,9 +58,9 @@ namespace Emby.Naming.Video
{ {
var filename = Path.GetFileName(path); var filename = Path.GetFileName(path);
var regex = new Regex(rule.Token, RegexOptions.IgnoreCase); var isMatch = Regex.IsMatch(filename, rule.Token, RegexOptions.IgnoreCase | RegexOptions.Compiled);
if (regex.IsMatch(filename)) if (isMatch)
{ {
result.ExtraType = rule.ExtraType; result.ExtraType = rule.ExtraType;
result.Rule = rule; result.Rule = rule;
+17 -12
View File
@@ -1,6 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using Jellyfin.Extensions;
namespace Emby.Naming.Video namespace Emby.Naming.Video
{ {
@@ -12,25 +12,30 @@ namespace Emby.Naming.Video
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="FileStack"/> class. /// Initializes a new instance of the <see cref="FileStack"/> class.
/// </summary> /// </summary>
public FileStack() /// <param name="name">The stack name.</param>
/// <param name="isDirectory">Whether the stack files are directories.</param>
/// <param name="files">The stack files.</param>
public FileStack(string name, bool isDirectory, IReadOnlyList<string> files)
{ {
Files = new List<string>(); Name = name;
IsDirectoryStack = isDirectory;
Files = files;
} }
/// <summary> /// <summary>
/// Gets or sets name of file stack. /// Gets the name of file stack.
/// </summary> /// </summary>
public string Name { get; set; } = string.Empty; public string Name { get; }
/// <summary> /// <summary>
/// Gets or sets list of paths in stack. /// Gets the list of paths in stack.
/// </summary> /// </summary>
public List<string> Files { get; set; } public IReadOnlyList<string> Files { get; }
/// <summary> /// <summary>
/// Gets or sets a value indicating whether stack is directory stack. /// Gets a value indicating whether stack is directory stack.
/// </summary> /// </summary>
public bool IsDirectoryStack { get; set; } public bool IsDirectoryStack { get; }
/// <summary> /// <summary>
/// Helper function to determine if path is in the stack. /// Helper function to determine if path is in the stack.
@@ -40,12 +45,12 @@ namespace Emby.Naming.Video
/// <returns>True if file is in the stack.</returns> /// <returns>True if file is in the stack.</returns>
public bool ContainsFile(string file, bool isDirectory) public bool ContainsFile(string file, bool isDirectory)
{ {
if (IsDirectoryStack == isDirectory) if (string.IsNullOrEmpty(file))
{ {
return Files.Contains(file, StringComparer.OrdinalIgnoreCase); return false;
} }
return false; return IsDirectoryStack == isDirectory && Files.Contains(file, StringComparison.OrdinalIgnoreCase);
} }
} }
} }
+48
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;
}
}
+1 -1
View File
@@ -9,7 +9,7 @@ namespace Emby.Naming.Video
public static class Format3DParser public static class Format3DParser
{ {
// Static default result to save on allocation costs. // Static default result to save on allocation costs.
private static readonly Format3DResult _defaultResult = new (false, null); private static readonly Format3DResult _defaultResult = new(false, null);
/// <summary> /// <summary>
/// Parse 3D format related flags. /// Parse 3D format related flags.
+74 -157
View File
@@ -2,7 +2,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text.RegularExpressions;
using Emby.Naming.AudioBook; using Emby.Naming.AudioBook;
using Emby.Naming.Common; using Emby.Naming.Common;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
@@ -12,37 +11,28 @@ namespace Emby.Naming.Video
/// <summary> /// <summary>
/// Resolve <see cref="FileStack"/> from list of paths. /// Resolve <see cref="FileStack"/> from list of paths.
/// </summary> /// </summary>
public class StackResolver public static class StackResolver
{ {
private readonly NamingOptions _options;
/// <summary>
/// Initializes a new instance of the <see cref="StackResolver"/> class.
/// </summary>
/// <param name="options"><see cref="NamingOptions"/> object containing VideoFileStackingRegexes and passes options to <see cref="VideoResolver"/>.</param>
public StackResolver(NamingOptions options)
{
_options = options;
}
/// <summary> /// <summary>
/// Resolves only directories from paths. /// Resolves only directories from paths.
/// </summary> /// </summary>
/// <param name="files">List of paths.</param> /// <param name="files">List of paths.</param>
/// <param name="namingOptions">The naming options.</param>
/// <returns>Enumerable <see cref="FileStack"/> of directories.</returns> /// <returns>Enumerable <see cref="FileStack"/> of directories.</returns>
public IEnumerable<FileStack> ResolveDirectories(IEnumerable<string> files) public static IEnumerable<FileStack> ResolveDirectories(IEnumerable<string> files, NamingOptions namingOptions)
{ {
return Resolve(files.Select(i => new FileSystemMetadata { FullName = i, IsDirectory = true })); return Resolve(files.Select(i => new FileSystemMetadata { FullName = i, IsDirectory = true }), namingOptions);
} }
/// <summary> /// <summary>
/// Resolves only files from paths. /// Resolves only files from paths.
/// </summary> /// </summary>
/// <param name="files">List of paths.</param> /// <param name="files">List of paths.</param>
/// <param name="namingOptions">The naming options.</param>
/// <returns>Enumerable <see cref="FileStack"/> of files.</returns> /// <returns>Enumerable <see cref="FileStack"/> of files.</returns>
public IEnumerable<FileStack> ResolveFiles(IEnumerable<string> files) public static IEnumerable<FileStack> ResolveFiles(IEnumerable<string> files, NamingOptions namingOptions)
{ {
return Resolve(files.Select(i => new FileSystemMetadata { FullName = i, IsDirectory = false })); return Resolve(files.Select(i => new FileSystemMetadata { FullName = i, IsDirectory = false }), namingOptions);
} }
/// <summary> /// <summary>
@@ -50,7 +40,7 @@ namespace Emby.Naming.Video
/// </summary> /// </summary>
/// <param name="files">List of paths.</param> /// <param name="files">List of paths.</param>
/// <returns>Enumerable <see cref="FileStack"/> of directories.</returns> /// <returns>Enumerable <see cref="FileStack"/> of directories.</returns>
public IEnumerable<FileStack> ResolveAudioBooks(IEnumerable<AudioBookFileInfo> files) public static IEnumerable<FileStack> ResolveAudioBooks(IEnumerable<AudioBookFileInfo> files)
{ {
var groupedDirectoryFiles = files.GroupBy(file => Path.GetDirectoryName(file.Path)); var groupedDirectoryFiles = files.GroupBy(file => Path.GetDirectoryName(file.Path));
@@ -60,19 +50,13 @@ namespace Emby.Naming.Video
{ {
foreach (var file in directory) foreach (var file in directory)
{ {
var stack = new FileStack { Name = Path.GetFileNameWithoutExtension(file.Path), IsDirectoryStack = false }; var stack = new FileStack(Path.GetFileNameWithoutExtension(file.Path), false, new[] { file.Path });
stack.Files.Add(file.Path);
yield return stack; yield return stack;
} }
} }
else else
{ {
var stack = new FileStack { Name = Path.GetFileName(directory.Key), IsDirectoryStack = false }; var stack = new FileStack(Path.GetFileName(directory.Key), false, directory.Select(f => f.Path).ToArray());
foreach (var file in directory)
{
stack.Files.Add(file.Path);
}
yield return stack; yield return stack;
} }
} }
@@ -82,158 +66,91 @@ namespace Emby.Naming.Video
/// Resolves videos from paths. /// Resolves videos from paths.
/// </summary> /// </summary>
/// <param name="files">List of paths.</param> /// <param name="files">List of paths.</param>
/// <param name="namingOptions">The naming options.</param>
/// <returns>Enumerable <see cref="FileStack"/> of videos.</returns> /// <returns>Enumerable <see cref="FileStack"/> of videos.</returns>
public IEnumerable<FileStack> Resolve(IEnumerable<FileSystemMetadata> files) public static IEnumerable<FileStack> Resolve(IEnumerable<FileSystemMetadata> files, NamingOptions namingOptions)
{ {
var list = files var potentialFiles = files
.Where(i => i.IsDirectory || VideoResolver.IsVideoFile(i.FullName, _options) || VideoResolver.IsStubFile(i.FullName, _options)) .Where(i => i.IsDirectory || VideoResolver.IsVideoFile(i.FullName, namingOptions) || VideoResolver.IsStubFile(i.FullName, namingOptions))
.OrderBy(i => i.FullName) .OrderBy(i => i.FullName);
.ToList();
var expressions = _options.VideoFileStackingRegexes; var potentialStacks = new Dictionary<string, StackMetadata>();
foreach (var file in potentialFiles)
for (var i = 0; i < list.Count; i++)
{ {
var offset = 0; var name = file.Name;
if (string.IsNullOrEmpty(name))
var file1 = list[i];
var expressionIndex = 0;
while (expressionIndex < expressions.Length)
{ {
var exp = expressions[expressionIndex]; name = Path.GetFileName(file.FullName);
var stack = new FileStack(); }
// (Title)(Volume)(Ignore)(Extension) for (var i = 0; i < namingOptions.VideoFileStackingRules.Length; i++)
var match1 = FindMatch(file1, exp, offset); {
var rule = namingOptions.VideoFileStackingRules[i];
if (match1.Success) if (!rule.Match(name, out var stackParsingResult))
{ {
var title1 = match1.Groups["title"].Value; continue;
var volume1 = match1.Groups["volume"].Value; }
var ignore1 = match1.Groups["ignore"].Value;
var extension1 = match1.Groups["extension"].Value;
var j = i + 1; var stackName = stackParsingResult.Value.StackName;
while (j < list.Count) var partNumber = stackParsingResult.Value.PartNumber;
var partType = stackParsingResult.Value.PartType;
if (!potentialStacks.TryGetValue(stackName, out var stackResult))
{
stackResult = new StackMetadata(file.IsDirectory, rule.IsNumerical, partType);
potentialStacks[stackName] = stackResult;
}
if (stackResult.Parts.Count > 0)
{
if (stackResult.IsDirectory != file.IsDirectory
|| !string.Equals(partType, stackResult.PartType, StringComparison.OrdinalIgnoreCase)
|| stackResult.ContainsPart(partNumber))
{ {
var file2 = list[j]; continue;
if (file1.IsDirectory != file2.IsDirectory)
{
j++;
continue;
}
// (Title)(Volume)(Ignore)(Extension)
var match2 = FindMatch(file2, exp, offset);
if (match2.Success)
{
var title2 = match2.Groups[1].Value;
var volume2 = match2.Groups[2].Value;
var ignore2 = match2.Groups[3].Value;
var extension2 = match2.Groups[4].Value;
if (string.Equals(title1, title2, StringComparison.OrdinalIgnoreCase))
{
if (!string.Equals(volume1, volume2, StringComparison.OrdinalIgnoreCase))
{
if (string.Equals(ignore1, ignore2, StringComparison.OrdinalIgnoreCase)
&& string.Equals(extension1, extension2, StringComparison.OrdinalIgnoreCase))
{
if (stack.Files.Count == 0)
{
stack.Name = title1 + ignore1;
stack.IsDirectoryStack = file1.IsDirectory;
stack.Files.Add(file1.FullName);
}
stack.Files.Add(file2.FullName);
}
else
{
// Sequel
offset = 0;
expressionIndex++;
break;
}
}
else if (!string.Equals(ignore1, ignore2, StringComparison.OrdinalIgnoreCase))
{
// False positive, try again with offset
offset = match1.Groups[3].Index;
break;
}
else
{
// Extension mismatch
offset = 0;
expressionIndex++;
break;
}
}
else
{
// Title mismatch
offset = 0;
expressionIndex++;
break;
}
}
else
{
// No match 2, next expression
offset = 0;
expressionIndex++;
break;
}
j++;
} }
if (j == list.Count) if (rule.IsNumerical != stackResult.IsNumerical)
{ {
expressionIndex = expressions.Length; break;
} }
} }
else
{
// No match 1
offset = 0;
expressionIndex++;
}
if (stack.Files.Count > 1) stackResult.Parts.Add(partNumber, file);
{ break;
yield return stack;
i += stack.Files.Count - 1;
break;
}
} }
} }
}
private static string GetRegexInput(FileSystemMetadata file) foreach (var (fileName, stack) in potentialStacks)
{
// For directories, dummy up an extension otherwise the expressions will fail
var input = !file.IsDirectory
? file.FullName
: file.FullName + ".mkv";
return Path.GetFileName(input);
}
private static Match FindMatch(FileSystemMetadata input, Regex regex, int offset)
{
var regexInput = GetRegexInput(input);
if (offset < 0 || offset >= regexInput.Length)
{ {
return Match.Empty; if (stack.Parts.Count < 2)
{
continue;
}
yield return new FileStack(fileName, stack.IsDirectory, stack.Parts.Select(kv => kv.Value.FullName).ToArray());
}
}
private class StackMetadata
{
public StackMetadata(bool isDirectory, bool isNumerical, string partType)
{
Parts = new Dictionary<string, FileSystemMetadata>(StringComparer.OrdinalIgnoreCase);
IsDirectory = isDirectory;
IsNumerical = isNumerical;
PartType = partType;
} }
return regex.Match(regexInput, offset); public Dictionary<string, FileSystemMetadata> Parts { get; }
public bool IsDirectory { get; }
public bool IsNumerical { get; }
public string PartType { get; }
public bool ContainsPart(string partNumber) => Parts.ContainsKey(partNumber);
} }
} }
} }
+2 -2
View File
@@ -1,7 +1,7 @@
using System; using System;
using System.IO; using System.IO;
using System.Linq;
using Emby.Naming.Common; using Emby.Naming.Common;
using Jellyfin.Extensions;
namespace Emby.Naming.Video namespace Emby.Naming.Video
{ {
@@ -28,7 +28,7 @@ namespace Emby.Naming.Video
var extension = Path.GetExtension(path); var extension = Path.GetExtension(path);
if (!options.StubFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)) if (!options.StubFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase))
{ {
return false; return false;
} }
+6 -7
View File
@@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using MediaBrowser.Model.Entities;
namespace Emby.Naming.Video namespace Emby.Naming.Video
{ {
@@ -17,7 +18,6 @@ namespace Emby.Naming.Video
Name = name; Name = name;
Files = Array.Empty<VideoFileInfo>(); Files = Array.Empty<VideoFileInfo>();
Extras = Array.Empty<VideoFileInfo>();
AlternateVersions = Array.Empty<VideoFileInfo>(); AlternateVersions = Array.Empty<VideoFileInfo>();
} }
@@ -39,16 +39,15 @@ namespace Emby.Naming.Video
/// <value>The files.</value> /// <value>The files.</value>
public IReadOnlyList<VideoFileInfo> Files { get; set; } public IReadOnlyList<VideoFileInfo> Files { get; set; }
/// <summary>
/// Gets or sets the extras.
/// </summary>
/// <value>The extras.</value>
public IReadOnlyList<VideoFileInfo> Extras { get; set; }
/// <summary> /// <summary>
/// Gets or sets the alternate versions. /// Gets or sets the alternate versions.
/// </summary> /// </summary>
/// <value>The alternate versions.</value> /// <value>The alternate versions.</value>
public IReadOnlyList<VideoFileInfo> AlternateVersions { get; set; } public IReadOnlyList<VideoFileInfo> AlternateVersions { get; set; }
/// <summary>
/// Gets or sets the extra type.
/// </summary>
public ExtraType? ExtraType { get; set; }
} }
} }
+33 -146
View File
@@ -4,7 +4,6 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Emby.Naming.Common; using Emby.Naming.Common;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
namespace Emby.Naming.Video namespace Emby.Naming.Video
@@ -17,29 +16,41 @@ namespace Emby.Naming.Video
/// <summary> /// <summary>
/// Resolves alternative versions and extras from list of video files. /// Resolves alternative versions and extras from list of video files.
/// </summary> /// </summary>
/// <param name="files">List of related video files.</param> /// <param name="videoInfos">List of related video files.</param>
/// <param name="namingOptions">The naming options.</param> /// <param name="namingOptions">The naming options.</param>
/// <param name="supportMultiVersion">Indication we should consider multi-versions of content.</param> /// <param name="supportMultiVersion">Indication we should consider multi-versions of content.</param>
/// <param name="parseName">Whether to parse the name or use the filename.</param>
/// <returns>Returns enumerable of <see cref="VideoInfo"/> which groups files together when related.</returns> /// <returns>Returns enumerable of <see cref="VideoInfo"/> which groups files together when related.</returns>
public static IEnumerable<VideoInfo> Resolve(IEnumerable<FileSystemMetadata> files, NamingOptions namingOptions, bool supportMultiVersion = true) public static IReadOnlyList<VideoInfo> Resolve(IReadOnlyList<VideoFileInfo> videoInfos, NamingOptions namingOptions, bool supportMultiVersion = true, bool parseName = true)
{ {
var videoInfos = files
.Select(i => VideoResolver.Resolve(i.FullName, i.IsDirectory, namingOptions))
.OfType<VideoFileInfo>()
.ToList();
// Filter out all extras, otherwise they could cause stacks to not be resolved // Filter out all extras, otherwise they could cause stacks to not be resolved
// See the unit test TestStackedWithTrailer // See the unit test TestStackedWithTrailer
var nonExtras = videoInfos var nonExtras = videoInfos
.Where(i => i.ExtraType == null) .Where(i => i.ExtraType == null)
.Select(i => new FileSystemMetadata { FullName = i.Path, IsDirectory = i.IsDirectory }); .Select(i => new FileSystemMetadata { FullName = i.Path, IsDirectory = i.IsDirectory });
var stackResult = new StackResolver(namingOptions) var stackResult = StackResolver.Resolve(nonExtras, namingOptions).ToList();
.Resolve(nonExtras).ToList();
var remainingFiles = videoInfos var remainingFiles = new List<VideoFileInfo>();
.Where(i => !stackResult.Any(s => i.Path != null && s.ContainsFile(i.Path, i.IsDirectory))) var standaloneMedia = new List<VideoFileInfo>();
.ToList();
for (var i = 0; i < videoInfos.Count; i++)
{
var current = videoInfos[i];
if (stackResult.Any(s => s.ContainsFile(current.Path, current.IsDirectory)))
{
continue;
}
if (current.ExtraType == null)
{
standaloneMedia.Add(current);
}
else
{
remainingFiles.Add(current);
}
}
var list = new List<VideoInfo>(); var list = new List<VideoInfo>();
@@ -47,38 +58,20 @@ namespace Emby.Naming.Video
{ {
var info = new VideoInfo(stack.Name) var info = new VideoInfo(stack.Name)
{ {
Files = stack.Files.Select(i => VideoResolver.Resolve(i, stack.IsDirectoryStack, namingOptions)) Files = stack.Files.Select(i => VideoResolver.Resolve(i, stack.IsDirectoryStack, namingOptions, parseName))
.OfType<VideoFileInfo>() .OfType<VideoFileInfo>()
.ToList() .ToList()
}; };
info.Year = info.Files[0].Year; info.Year = info.Files[0].Year;
var extras = ExtractExtras(remainingFiles, stack.Name, Path.GetFileNameWithoutExtension(stack.Files[0].AsSpan()), namingOptions.VideoFlagDelimiters);
if (extras.Count > 0)
{
info.Extras = extras;
}
list.Add(info); list.Add(info);
} }
var standaloneMedia = remainingFiles
.Where(i => i.ExtraType == null)
.ToList();
foreach (var media in standaloneMedia) foreach (var media in standaloneMedia)
{ {
var info = new VideoInfo(media.Name) { Files = new[] { media } }; var info = new VideoInfo(media.Name) { Files = new[] { media } };
info.Year = info.Files[0].Year; info.Year = info.Files[0].Year;
remainingFiles.Remove(media);
var extras = ExtractExtras(remainingFiles, media.FileNameWithoutExtension, namingOptions.VideoFlagDelimiters);
info.Extras = extras;
list.Add(info); list.Add(info);
} }
@@ -87,58 +80,12 @@ namespace Emby.Naming.Video
list = GetVideosGroupedByVersion(list, namingOptions); list = GetVideosGroupedByVersion(list, namingOptions);
} }
// If there's only one resolved video, use the folder name as well to find extras
if (list.Count == 1)
{
var info = list[0];
var videoPath = list[0].Files[0].Path;
var parentPath = Path.GetDirectoryName(videoPath.AsSpan());
if (!parentPath.IsEmpty)
{
var folderName = Path.GetFileName(parentPath);
if (!folderName.IsEmpty)
{
var extras = ExtractExtras(remainingFiles, folderName, namingOptions.VideoFlagDelimiters);
extras.AddRange(info.Extras);
info.Extras = extras;
}
}
// Add the extras that are just based on file name as well
var extrasByFileName = remainingFiles
.Where(i => i.ExtraRule != null && i.ExtraRule.RuleType == ExtraRuleType.Filename)
.ToList();
remainingFiles = remainingFiles
.Except(extrasByFileName)
.ToList();
extrasByFileName.AddRange(info.Extras);
info.Extras = extrasByFileName;
}
// If there's only one video, accept all trailers
// Be lenient because people use all kinds of mishmash conventions with trailers.
if (list.Count == 1)
{
var trailers = remainingFiles
.Where(i => i.ExtraType == ExtraType.Trailer)
.ToList();
trailers.AddRange(list[0].Extras);
list[0].Extras = trailers;
remainingFiles = remainingFiles
.Except(trailers)
.ToList();
}
// Whatever files are left, just add them // Whatever files are left, just add them
list.AddRange(remainingFiles.Select(i => new VideoInfo(i.Name) list.AddRange(remainingFiles.Select(i => new VideoInfo(i.Name)
{ {
Files = new[] { i }, Files = new[] { i },
Year = i.Year Year = i.Year,
ExtraType = i.ExtraType
})); }));
return list; return list;
@@ -162,6 +109,11 @@ namespace Emby.Naming.Video
for (var i = 0; i < videos.Count; i++) for (var i = 0; i < videos.Count; i++)
{ {
var video = videos[i]; var video = videos[i];
if (video.ExtraType != null)
{
continue;
}
if (!IsEligibleForMultiVersion(folderName, video.Files[0].Path, namingOptions)) if (!IsEligibleForMultiVersion(folderName, video.Files[0].Path, namingOptions))
{ {
return videos; return videos;
@@ -178,17 +130,14 @@ namespace Emby.Naming.Video
var alternateVersionsLen = videos.Count - 1; var alternateVersionsLen = videos.Count - 1;
var alternateVersions = new VideoFileInfo[alternateVersionsLen]; var alternateVersions = new VideoFileInfo[alternateVersionsLen];
var extras = new List<VideoFileInfo>(list[0].Extras);
for (int i = 0; i < alternateVersionsLen; i++) for (int i = 0; i < alternateVersionsLen; i++)
{ {
var video = videos[i + 1]; var video = videos[i + 1];
alternateVersions[i] = video.Files[0]; alternateVersions[i] = video.Files[0];
extras.AddRange(video.Extras);
} }
list[0].AlternateVersions = alternateVersions; list[0].AlternateVersions = alternateVersions;
list[0].Name = folderName.ToString(); list[0].Name = folderName.ToString();
list[0].Extras = extras;
return list; return list;
} }
@@ -230,7 +179,7 @@ namespace Emby.Naming.Video
var tmpTestFilename = testFilename.ToString(); var tmpTestFilename = testFilename.ToString();
if (CleanStringParser.TryClean(tmpTestFilename, namingOptions.CleanStringRegexes, out var cleanName)) if (CleanStringParser.TryClean(tmpTestFilename, namingOptions.CleanStringRegexes, out var cleanName))
{ {
tmpTestFilename = cleanName.Trim().ToString(); tmpTestFilename = cleanName.Trim();
} }
// The CleanStringParser should have removed common keywords etc. // The CleanStringParser should have removed common keywords etc.
@@ -238,67 +187,5 @@ namespace Emby.Naming.Video
|| testFilename[0] == '-' || testFilename[0] == '-'
|| Regex.IsMatch(tmpTestFilename, @"^\[([^]]*)\]", RegexOptions.Compiled); || Regex.IsMatch(tmpTestFilename, @"^\[([^]]*)\]", RegexOptions.Compiled);
} }
private static ReadOnlySpan<char> TrimFilenameDelimiters(ReadOnlySpan<char> name, ReadOnlySpan<char> videoFlagDelimiters)
{
return name.IsEmpty ? name : name.TrimEnd().TrimEnd(videoFlagDelimiters).TrimEnd();
}
private static bool StartsWith(ReadOnlySpan<char> fileName, ReadOnlySpan<char> baseName, ReadOnlySpan<char> trimmedBaseName)
{
if (baseName.IsEmpty)
{
return false;
}
return fileName.StartsWith(baseName, StringComparison.OrdinalIgnoreCase)
|| (!trimmedBaseName.IsEmpty && fileName.StartsWith(trimmedBaseName, StringComparison.OrdinalIgnoreCase));
}
/// <summary>
/// Finds similar filenames to that of [baseName] and removes any matches from [remainingFiles].
/// </summary>
/// <param name="remainingFiles">The list of remaining filenames.</param>
/// <param name="baseName">The base name to use for the comparison.</param>
/// <param name="videoFlagDelimiters">The video flag delimiters.</param>
/// <returns>A list of video extras for [baseName].</returns>
private static List<VideoFileInfo> ExtractExtras(IList<VideoFileInfo> remainingFiles, ReadOnlySpan<char> baseName, ReadOnlySpan<char> videoFlagDelimiters)
{
return ExtractExtras(remainingFiles, baseName, ReadOnlySpan<char>.Empty, videoFlagDelimiters);
}
/// <summary>
/// Finds similar filenames to that of [firstBaseName] and [secondBaseName] and removes any matches from [remainingFiles].
/// </summary>
/// <param name="remainingFiles">The list of remaining filenames.</param>
/// <param name="firstBaseName">The first base name to use for the comparison.</param>
/// <param name="secondBaseName">The second base name to use for the comparison.</param>
/// <param name="videoFlagDelimiters">The video flag delimiters.</param>
/// <returns>A list of video extras for [firstBaseName] and [secondBaseName].</returns>
private static List<VideoFileInfo> ExtractExtras(IList<VideoFileInfo> remainingFiles, ReadOnlySpan<char> firstBaseName, ReadOnlySpan<char> secondBaseName, ReadOnlySpan<char> videoFlagDelimiters)
{
var trimmedFirstBaseName = TrimFilenameDelimiters(firstBaseName, videoFlagDelimiters);
var trimmedSecondBaseName = TrimFilenameDelimiters(secondBaseName, videoFlagDelimiters);
var result = new List<VideoFileInfo>();
for (var pos = remainingFiles.Count - 1; pos >= 0; pos--)
{
var file = remainingFiles[pos];
if (file.ExtraType == null)
{
continue;
}
var filename = file.FileNameWithoutExtension;
if (StartsWith(filename, firstBaseName, trimmedFirstBaseName)
|| StartsWith(filename, secondBaseName, trimmedSecondBaseName))
{
result.Add(file);
remainingFiles.RemoveAt(pos);
}
}
return result;
}
} }
} }
+4 -3
View File
@@ -16,10 +16,11 @@ namespace Emby.Naming.Video
/// </summary> /// </summary>
/// <param name="path">The path.</param> /// <param name="path">The path.</param>
/// <param name="namingOptions">The naming options.</param> /// <param name="namingOptions">The naming options.</param>
/// <param name="parseName">Whether to parse the name or use the filename.</param>
/// <returns>VideoFileInfo.</returns> /// <returns>VideoFileInfo.</returns>
public static VideoFileInfo? ResolveDirectory(string? path, NamingOptions namingOptions) public static VideoFileInfo? ResolveDirectory(string? path, NamingOptions namingOptions, bool parseName = true)
{ {
return Resolve(path, true, namingOptions); return Resolve(path, true, namingOptions, parseName);
} }
/// <summary> /// <summary>
@@ -74,7 +75,7 @@ namespace Emby.Naming.Video
var format3DResult = Format3DParser.Parse(path, namingOptions); var format3DResult = Format3DParser.Parse(path, namingOptions);
var extraResult = new ExtraResolver(namingOptions).GetExtraInfo(path); var extraResult = ExtraRuleResolver.GetExtraInfo(path, namingOptions);
var name = Path.GetFileNameWithoutExtension(path); var name = Path.GetFileNameWithoutExtension(path);
+16 -16
View File
@@ -24,63 +24,63 @@ namespace Emby.Notifications
{ {
new NotificationTypeInfo new NotificationTypeInfo
{ {
Type = NotificationType.ApplicationUpdateInstalled.ToString() Type = nameof(NotificationType.ApplicationUpdateInstalled)
}, },
new NotificationTypeInfo new NotificationTypeInfo
{ {
Type = NotificationType.InstallationFailed.ToString() Type = nameof(NotificationType.InstallationFailed)
}, },
new NotificationTypeInfo new NotificationTypeInfo
{ {
Type = NotificationType.PluginInstalled.ToString() Type = nameof(NotificationType.PluginInstalled)
}, },
new NotificationTypeInfo new NotificationTypeInfo
{ {
Type = NotificationType.PluginError.ToString() Type = nameof(NotificationType.PluginError)
}, },
new NotificationTypeInfo new NotificationTypeInfo
{ {
Type = NotificationType.PluginUninstalled.ToString() Type = nameof(NotificationType.PluginUninstalled)
}, },
new NotificationTypeInfo new NotificationTypeInfo
{ {
Type = NotificationType.PluginUpdateInstalled.ToString() Type = nameof(NotificationType.PluginUpdateInstalled)
}, },
new NotificationTypeInfo new NotificationTypeInfo
{ {
Type = NotificationType.ServerRestartRequired.ToString() Type = nameof(NotificationType.ServerRestartRequired)
}, },
new NotificationTypeInfo new NotificationTypeInfo
{ {
Type = NotificationType.TaskFailed.ToString() Type = nameof(NotificationType.TaskFailed)
}, },
new NotificationTypeInfo new NotificationTypeInfo
{ {
Type = NotificationType.NewLibraryContent.ToString() Type = nameof(NotificationType.NewLibraryContent)
}, },
new NotificationTypeInfo new NotificationTypeInfo
{ {
Type = NotificationType.AudioPlayback.ToString() Type = nameof(NotificationType.AudioPlayback)
}, },
new NotificationTypeInfo new NotificationTypeInfo
{ {
Type = NotificationType.VideoPlayback.ToString() Type = nameof(NotificationType.VideoPlayback)
}, },
new NotificationTypeInfo new NotificationTypeInfo
{ {
Type = NotificationType.AudioPlaybackStopped.ToString() Type = nameof(NotificationType.AudioPlaybackStopped)
}, },
new NotificationTypeInfo new NotificationTypeInfo
{ {
Type = NotificationType.VideoPlaybackStopped.ToString() Type = nameof(NotificationType.VideoPlaybackStopped)
}, },
new NotificationTypeInfo new NotificationTypeInfo
{ {
Type = NotificationType.UserLockedOut.ToString() Type = nameof(NotificationType.UserLockedOut)
}, },
new NotificationTypeInfo new NotificationTypeInfo
{ {
Type = NotificationType.ApplicationUpdateAvailable.ToString() Type = nameof(NotificationType.ApplicationUpdateAvailable)
} }
}; };
@@ -98,7 +98,7 @@ namespace Emby.Notifications
private void Update(NotificationTypeInfo note) private void Update(NotificationTypeInfo note)
{ {
note.Name = _localization.GetLocalizedString("NotificationOption" + note.Type) ?? note.Type; note.Name = _localization.GetLocalizedString("NotificationOption" + note.Type);
note.IsBasedOnUserEvent = note.Type.IndexOf("Playback", StringComparison.OrdinalIgnoreCase) != -1; note.IsBasedOnUserEvent = note.Type.IndexOf("Playback", StringComparison.OrdinalIgnoreCase) != -1;
+1 -1
View File
@@ -24,7 +24,7 @@
<!-- Code analyzers--> <!-- Code analyzers-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' "> <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" /> <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup> </ItemGroup>
+2 -1
View File
@@ -5,6 +5,7 @@ using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Data.Events; using Jellyfin.Data.Events;
using Jellyfin.Extensions;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller; using MediaBrowser.Controller;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
@@ -104,7 +105,7 @@ namespace Emby.Notifications
var type = entry.Type; var type = entry.Type;
if (string.IsNullOrEmpty(type) || !_coreNotificationTypes.Contains(type, StringComparer.OrdinalIgnoreCase)) if (string.IsNullOrEmpty(type) || !_coreNotificationTypes.Contains(type, StringComparison.OrdinalIgnoreCase))
{ {
return; return;
} }
+1 -1
View File
@@ -26,7 +26,7 @@
<!-- Code Analyzers--> <!-- Code Analyzers-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' "> <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="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup> </ItemGroup>
+2 -1
View File
@@ -3,6 +3,7 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
@@ -60,7 +61,7 @@ namespace Emby.Photos
item.SetImagePath(ImageType.Primary, item.Path); item.SetImagePath(ImageType.Primary, item.Path);
// Examples: https://github.com/mono/taglib-sharp/blob/a5f6949a53d09ce63ee7495580d6802921a21f14/tests/fixtures/TagLib.Tests.Images/NullOrientationTest.cs // Examples: https://github.com/mono/taglib-sharp/blob/a5f6949a53d09ce63ee7495580d6802921a21f14/tests/fixtures/TagLib.Tests.Images/NullOrientationTest.cs
if (_includeExtensions.Contains(Path.GetExtension(item.Path), StringComparer.OrdinalIgnoreCase)) if (_includeExtensions.Contains(Path.GetExtension(item.Path), StringComparison.OrdinalIgnoreCase))
{ {
try try
{ {
@@ -301,7 +301,7 @@ namespace Emby.Server.Implementations.AppBase
{ {
return _configurations.GetOrAdd( return _configurations.GetOrAdd(
key, key,
(k, configurationManager) => static (k, configurationManager) =>
{ {
var file = configurationManager.GetConfigurationFile(k); var file = configurationManager.GetConfigurationFile(k);
@@ -371,7 +371,7 @@ namespace Emby.Server.Implementations.AppBase
NewConfiguration = configuration NewConfiguration = configuration
}); });
_configurations.AddOrUpdate(key, configuration, (k, v) => configuration); _configurations.AddOrUpdate(key, configuration, (_, _) => configuration);
var path = GetConfigurationFile(key); var path = GetConfigurationFile(key);
Directory.CreateDirectory(Path.GetDirectoryName(path)); Directory.CreateDirectory(Path.GetDirectoryName(path));
@@ -1,6 +1,5 @@
using System; using System;
using System.IO; using System.IO;
using System.Linq;
using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Serialization;
namespace Emby.Server.Implementations.AppBase namespace Emby.Server.Implementations.AppBase
@@ -120,7 +120,7 @@ namespace Emby.Server.Implementations
/// <summary> /// <summary>
/// The disposable parts. /// The disposable parts.
/// </summary> /// </summary>
private readonly ConcurrentDictionary<IDisposable, byte> _disposableParts = new (); private readonly ConcurrentDictionary<IDisposable, byte> _disposableParts = new();
private readonly IFileSystem _fileSystemManager; private readonly IFileSystem _fileSystemManager;
private readonly IConfiguration _startupConfig; private readonly IConfiguration _startupConfig;
@@ -1,11 +1,8 @@
using System.IO; using System.IO;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using SharpCompress.Archives.SevenZip;
using SharpCompress.Archives.Tar;
using SharpCompress.Common; using SharpCompress.Common;
using SharpCompress.Readers; using SharpCompress.Readers;
using SharpCompress.Readers.GZip; using SharpCompress.Readers.GZip;
using SharpCompress.Readers.Zip;
namespace Emby.Server.Implementations.Archiving namespace Emby.Server.Implementations.Archiving
{ {
@@ -14,55 +11,6 @@ namespace Emby.Server.Implementations.Archiving
/// </summary> /// </summary>
public class ZipClient : IZipClient public class ZipClient : IZipClient
{ {
/// <summary>
/// Extracts all.
/// </summary>
/// <param name="sourceFile">The source file.</param>
/// <param name="targetPath">The target path.</param>
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
public void ExtractAll(string sourceFile, string targetPath, bool overwriteExistingFiles)
{
using var fileStream = File.OpenRead(sourceFile);
ExtractAll(fileStream, targetPath, overwriteExistingFiles);
}
/// <summary>
/// Extracts all.
/// </summary>
/// <param name="source">The source.</param>
/// <param name="targetPath">The target path.</param>
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
public void ExtractAll(Stream source, string targetPath, bool overwriteExistingFiles)
{
using var reader = ReaderFactory.Open(source);
var options = new ExtractionOptions
{
ExtractFullPath = true
};
if (overwriteExistingFiles)
{
options.Overwrite = true;
}
Directory.CreateDirectory(targetPath);
reader.WriteAllToDirectory(targetPath, options);
}
/// <inheritdoc />
public void ExtractAllFromZip(Stream source, string targetPath, bool overwriteExistingFiles)
{
using var reader = ZipReader.Open(source);
var options = new ExtractionOptions
{
ExtractFullPath = true,
Overwrite = overwriteExistingFiles
};
Directory.CreateDirectory(targetPath);
reader.WriteAllToDirectory(targetPath, options);
}
/// <inheritdoc /> /// <inheritdoc />
public void ExtractAllFromGz(Stream source, string targetPath, bool overwriteExistingFiles) public void ExtractAllFromGz(Stream source, string targetPath, bool overwriteExistingFiles)
{ {
@@ -94,69 +42,5 @@ namespace Emby.Server.Implementations.Archiving
reader.WriteEntryToFile(Path.Combine(targetPath, filename)); reader.WriteEntryToFile(Path.Combine(targetPath, filename));
} }
} }
/// <summary>
/// Extracts all from7z.
/// </summary>
/// <param name="sourceFile">The source file.</param>
/// <param name="targetPath">The target path.</param>
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
public void ExtractAllFrom7z(string sourceFile, string targetPath, bool overwriteExistingFiles)
{
using var fileStream = File.OpenRead(sourceFile);
ExtractAllFrom7z(fileStream, targetPath, overwriteExistingFiles);
}
/// <summary>
/// Extracts all from7z.
/// </summary>
/// <param name="source">The source.</param>
/// <param name="targetPath">The target path.</param>
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
public void ExtractAllFrom7z(Stream source, string targetPath, bool overwriteExistingFiles)
{
using var archive = SevenZipArchive.Open(source);
using var reader = archive.ExtractAllEntries();
var options = new ExtractionOptions
{
ExtractFullPath = true,
Overwrite = overwriteExistingFiles
};
Directory.CreateDirectory(targetPath);
reader.WriteAllToDirectory(targetPath, options);
}
/// <summary>
/// Extracts all from tar.
/// </summary>
/// <param name="sourceFile">The source file.</param>
/// <param name="targetPath">The target path.</param>
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
public void ExtractAllFromTar(string sourceFile, string targetPath, bool overwriteExistingFiles)
{
using var fileStream = File.OpenRead(sourceFile);
ExtractAllFromTar(fileStream, targetPath, overwriteExistingFiles);
}
/// <summary>
/// Extracts all from tar.
/// </summary>
/// <param name="source">The source.</param>
/// <param name="targetPath">The target path.</param>
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
public void ExtractAllFromTar(Stream source, string targetPath, bool overwriteExistingFiles)
{
using var archive = TarArchive.Open(source);
using var reader = archive.ExtractAllEntries();
var options = new ExtractionOptions
{
ExtractFullPath = true,
Overwrite = overwriteExistingFiles
};
Directory.CreateDirectory(targetPath);
reader.WriteAllToDirectory(targetPath, options);
}
} }
} }
@@ -10,6 +10,7 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Data.Entities; using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using Jellyfin.Extensions.Json; using Jellyfin.Extensions.Json;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Progress; using MediaBrowser.Common.Progress;
@@ -129,16 +130,14 @@ namespace Emby.Server.Implementations.Channels
var internalChannel = _libraryManager.GetItemById(item.ChannelId); var internalChannel = _libraryManager.GetItemById(item.ChannelId);
if (internalChannel == null) if (internalChannel == null)
{ {
throw new ArgumentException(); throw new ArgumentException(nameof(item.ChannelId));
} }
var channel = Channels.FirstOrDefault(i => GetInternalChannelId(i.Name).Equals(internalChannel.Id)); var channel = Channels.FirstOrDefault(i => GetInternalChannelId(i.Name).Equals(internalChannel.Id));
var supportsDelete = channel as ISupportsDelete; if (channel is not ISupportsDelete supportsDelete)
if (supportsDelete == null)
{ {
throw new ArgumentException(); throw new ArgumentException(nameof(channel));
} }
return supportsDelete.DeleteItem(item.ExternalId, CancellationToken.None); return supportsDelete.DeleteItem(item.ExternalId, CancellationToken.None);
@@ -179,7 +178,7 @@ namespace Emby.Server.Implementations.Channels
try try
{ {
return (GetChannelProvider(i) is IHasFolderAttributes hasAttributes return (GetChannelProvider(i) is IHasFolderAttributes hasAttributes
&& hasAttributes.Attributes.Contains("Recordings", StringComparer.OrdinalIgnoreCase)) == val; && hasAttributes.Attributes.Contains("Recordings", StringComparison.OrdinalIgnoreCase)) == val;
} }
catch catch
{ {
@@ -541,7 +540,7 @@ namespace Emby.Server.Implementations.Channels
return _libraryManager.GetItemIds( return _libraryManager.GetItemIds(
new InternalItemsQuery new InternalItemsQuery
{ {
IncludeItemTypes = new[] { nameof(Channel) }, IncludeItemTypes = new[] { BaseItemKind.Channel },
OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) } OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) }
}).Select(i => GetChannelFeatures(i)).ToArray(); }).Select(i => GetChannelFeatures(i)).ToArray();
} }
@@ -1135,7 +1134,7 @@ namespace Emby.Server.Implementations.Channels
if (!info.IsLiveStream) if (!info.IsLiveStream)
{ {
if (item.Tags.Contains("livestream", StringComparer.OrdinalIgnoreCase)) if (item.Tags.Contains("livestream", StringComparison.OrdinalIgnoreCase))
{ {
item.Tags = item.Tags.Except(new[] { "livestream" }, StringComparer.OrdinalIgnoreCase).ToArray(); item.Tags = item.Tags.Except(new[] { "livestream" }, StringComparer.OrdinalIgnoreCase).ToArray();
_logger.LogDebug("Forcing update due to Tags {0}", item.Name); _logger.LogDebug("Forcing update due to Tags {0}", item.Name);
@@ -1144,7 +1143,7 @@ namespace Emby.Server.Implementations.Channels
} }
else else
{ {
if (!item.Tags.Contains("livestream", StringComparer.OrdinalIgnoreCase)) if (!item.Tags.Contains("livestream", StringComparison.OrdinalIgnoreCase))
{ {
item.Tags = item.Tags.Concat(new[] { "livestream" }).ToArray(); item.Tags = item.Tags.Concat(new[] { "livestream" }).ToArray();
_logger.LogDebug("Forcing update due to Tags {0}", item.Name); _logger.LogDebug("Forcing update due to Tags {0}", item.Name);
@@ -2,6 +2,7 @@ using System;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
@@ -51,7 +52,7 @@ namespace Emby.Server.Implementations.Channels
var uninstalledChannels = _libraryManager.GetItemList(new InternalItemsQuery var uninstalledChannels = _libraryManager.GetItemList(new InternalItemsQuery
{ {
IncludeItemTypes = new[] { nameof(Channel) }, IncludeItemTypes = new[] { BaseItemKind.Channel },
ExcludeItemIds = installedChannelIds.ToArray() ExcludeItemIds = installedChannelIds.ToArray()
}); });
@@ -140,7 +140,7 @@ namespace Emby.Server.Implementations.Collections
if (parentFolder == null) if (parentFolder == null)
{ {
throw new ArgumentException(); throw new ArgumentException(nameof(parentFolder));
} }
var path = Path.Combine(parentFolder.Path, folderName); var path = Path.Combine(parentFolder.Path, folderName);
@@ -4,8 +4,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Threading; using System.Threading;
using Jellyfin.Extensions;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using SQLitePCL.pretty; using SQLitePCL.pretty;
@@ -160,21 +160,22 @@ namespace Emby.Server.Implementations.Data
protected bool TableExists(ManagedConnection connection, string name) protected bool TableExists(ManagedConnection connection, string name)
{ {
return connection.RunInTransaction( return connection.RunInTransaction(
db => db =>
{
using (var statement = PrepareStatement(db, "select DISTINCT tbl_name from sqlite_master"))
{ {
foreach (var row in statement.ExecuteQuery()) using (var statement = PrepareStatement(db, "select DISTINCT tbl_name from sqlite_master"))
{ {
if (string.Equals(name, row.GetString(0), StringComparison.OrdinalIgnoreCase)) foreach (var row in statement.ExecuteQuery())
{ {
return true; if (string.Equals(name, row.GetString(0), StringComparison.OrdinalIgnoreCase))
{
return true;
}
} }
} }
}
return false; return false;
}, ReadTransactionMode); },
ReadTransactionMode);
} }
protected List<string> GetColumnNames(IDatabaseConnection connection, string table) protected List<string> GetColumnNames(IDatabaseConnection connection, string table)
@@ -194,7 +195,7 @@ namespace Emby.Server.Implementations.Data
protected void AddColumn(IDatabaseConnection connection, string table, string columnName, string type, List<string> existingColumnNames) protected void AddColumn(IDatabaseConnection connection, string table, string columnName, string type, List<string> existingColumnNames)
{ {
if (existingColumnNames.Contains(columnName, StringComparer.OrdinalIgnoreCase)) if (existingColumnNames.Contains(columnName, StringComparison.OrdinalIgnoreCase))
{ {
return; return;
} }
File diff suppressed because it is too large Load Diff
@@ -50,41 +50,42 @@ namespace Emby.Server.Implementations.Data
var users = userDatasTableExists ? null : userManager.Users; var users = userDatasTableExists ? null : userManager.Users;
connection.RunInTransaction( connection.RunInTransaction(
db => db =>
{
db.ExecuteAll(string.Join(';', new[]
{ {
"create table if not exists UserDatas (key nvarchar not null, userId INT not null, rating float null, played bit not null, playCount int not null, isFavorite bit not null, playbackPositionTicks bigint not null, lastPlayedDate datetime null, AudioStreamIndex INT, SubtitleStreamIndex INT)", db.ExecuteAll(string.Join(';', new[]
"drop index if exists idx_userdata",
"drop index if exists idx_userdata1",
"drop index if exists idx_userdata2",
"drop index if exists userdataindex1",
"drop index if exists userdataindex",
"drop index if exists userdataindex3",
"drop index if exists userdataindex4",
"create unique index if not exists UserDatasIndex1 on UserDatas (key, userId)",
"create index if not exists UserDatasIndex2 on UserDatas (key, userId, played)",
"create index if not exists UserDatasIndex3 on UserDatas (key, userId, playbackPositionTicks)",
"create index if not exists UserDatasIndex4 on UserDatas (key, userId, isFavorite)"
}));
if (userDataTableExists)
{
var existingColumnNames = GetColumnNames(db, "userdata");
AddColumn(db, "userdata", "InternalUserId", "int", existingColumnNames);
AddColumn(db, "userdata", "AudioStreamIndex", "int", existingColumnNames);
AddColumn(db, "userdata", "SubtitleStreamIndex", "int", existingColumnNames);
if (!userDatasTableExists)
{ {
ImportUserIds(db, users); "create table if not exists UserDatas (key nvarchar not null, userId INT not null, rating float null, played bit not null, playCount int not null, isFavorite bit not null, playbackPositionTicks bigint not null, lastPlayedDate datetime null, AudioStreamIndex INT, SubtitleStreamIndex INT)",
db.ExecuteAll("INSERT INTO UserDatas (key, userId, rating, played, playCount, isFavorite, playbackPositionTicks, lastPlayedDate, AudioStreamIndex, SubtitleStreamIndex) SELECT key, InternalUserId, rating, played, playCount, isFavorite, playbackPositionTicks, lastPlayedDate, AudioStreamIndex, SubtitleStreamIndex from userdata where InternalUserId not null"); "drop index if exists idx_userdata",
"drop index if exists idx_userdata1",
"drop index if exists idx_userdata2",
"drop index if exists userdataindex1",
"drop index if exists userdataindex",
"drop index if exists userdataindex3",
"drop index if exists userdataindex4",
"create unique index if not exists UserDatasIndex1 on UserDatas (key, userId)",
"create index if not exists UserDatasIndex2 on UserDatas (key, userId, played)",
"create index if not exists UserDatasIndex3 on UserDatas (key, userId, playbackPositionTicks)",
"create index if not exists UserDatasIndex4 on UserDatas (key, userId, isFavorite)"
}));
if (userDataTableExists)
{
var existingColumnNames = GetColumnNames(db, "userdata");
AddColumn(db, "userdata", "InternalUserId", "int", existingColumnNames);
AddColumn(db, "userdata", "AudioStreamIndex", "int", existingColumnNames);
AddColumn(db, "userdata", "SubtitleStreamIndex", "int", existingColumnNames);
if (!userDatasTableExists)
{
ImportUserIds(db, users);
db.ExecuteAll("INSERT INTO UserDatas (key, userId, rating, played, playCount, isFavorite, playbackPositionTicks, lastPlayedDate, AudioStreamIndex, SubtitleStreamIndex) SELECT key, InternalUserId, rating, played, playCount, isFavorite, playbackPositionTicks, lastPlayedDate, AudioStreamIndex, SubtitleStreamIndex from userdata where InternalUserId not null");
}
} }
} },
}, TransactionMode); TransactionMode);
} }
} }
@@ -183,10 +184,11 @@ namespace Emby.Server.Implementations.Data
using (var connection = GetConnection()) using (var connection = GetConnection())
{ {
connection.RunInTransaction( connection.RunInTransaction(
db => db =>
{ {
SaveUserData(db, internalUserId, key, userData); SaveUserData(db, internalUserId, key, userData);
}, TransactionMode); },
TransactionMode);
} }
} }
@@ -252,13 +254,14 @@ namespace Emby.Server.Implementations.Data
using (var connection = GetConnection()) using (var connection = GetConnection())
{ {
connection.RunInTransaction( connection.RunInTransaction(
db => db =>
{
foreach (var userItemData in userDataList)
{ {
SaveUserData(db, internalUserId, userItemData.Key, userItemData); foreach (var userItemData in userDataList)
} {
}, TransactionMode); SaveUserData(db, internalUserId, userItemData.Key, userItemData);
}
},
TransactionMode);
} }
} }
@@ -37,7 +37,7 @@ namespace Emby.Server.Implementations.Devices
{ {
var value = File.ReadAllText(CachePath, Encoding.UTF8); var value = File.ReadAllText(CachePath, Encoding.UTF8);
if (Guid.TryParse(value, out var guid)) if (Guid.TryParse(value, out _))
{ {
return value; return value;
} }
+15 -33
View File
@@ -7,9 +7,9 @@ using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
using Jellyfin.Data.Entities; using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Common; using MediaBrowser.Common;
using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Drawing;
@@ -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) new DtoOptions(false)
{ {
EnableImages = false EnableImages = false
}), }));
user);
} }
return dto; return dto;
@@ -294,7 +293,7 @@ namespace Emby.Server.Implementations.Dto
path = path.TrimStart('.'); path = path.TrimStart('.');
} }
if (!string.IsNullOrEmpty(path) && containers.Contains(path, StringComparer.OrdinalIgnoreCase)) if (!string.IsNullOrEmpty(path) && containers.Contains(path, StringComparison.OrdinalIgnoreCase))
{ {
fileExtensionContainer = path; fileExtensionContainer = path;
} }
@@ -311,13 +310,13 @@ namespace Emby.Server.Implementations.Dto
if (taggedItems != null && options.ContainsField(ItemFields.ItemCounts)) if (taggedItems != null && options.ContainsField(ItemFields.ItemCounts))
{ {
SetItemByNameInfo(item, dto, taggedItems, user); SetItemByNameInfo(item, dto, taggedItems);
} }
return dto; return dto;
} }
private static void SetItemByNameInfo(BaseItem item, BaseItemDto dto, IList<BaseItem> taggedItems, User user = null) private static void SetItemByNameInfo(BaseItem item, BaseItemDto dto, IList<BaseItem> taggedItems)
{ {
if (item is MusicArtist) if (item is MusicArtist)
{ {
@@ -470,7 +469,7 @@ namespace Emby.Server.Implementations.Dto
{ {
var parentAlbumIds = _libraryManager.GetItemIds(new InternalItemsQuery var parentAlbumIds = _libraryManager.GetItemIds(new InternalItemsQuery
{ {
IncludeItemTypes = new[] { nameof(MusicAlbum) }, IncludeItemTypes = new[] { BaseItemKind.MusicAlbum },
Name = item.Album, Name = item.Album,
Limit = 1 Limit = 1
}); });
@@ -1404,44 +1403,27 @@ namespace Emby.Server.Implementations.Dto
return null; return null;
} }
ImageDimensions size;
var defaultAspectRatio = item.GetDefaultPrimaryImageAspectRatio();
if (defaultAspectRatio > 0)
{
return defaultAspectRatio;
}
if (!imageInfo.IsLocalFile) if (!imageInfo.IsLocalFile)
{ {
return null; return item.GetDefaultPrimaryImageAspectRatio();
} }
try try
{ {
size = _imageProcessor.GetImageDimensions(item, imageInfo); var size = _imageProcessor.GetImageDimensions(item, imageInfo);
var width = size.Width;
if (size.Width <= 0 || size.Height <= 0) var height = size.Height;
if (width > 0 && height > 0)
{ {
return null; return (double)width / height;
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogError(ex, "Failed to determine primary image aspect ratio for {0}", imageInfo.Path); _logger.LogError(ex, "Failed to determine primary image aspect ratio for {ImagePath}", imageInfo.Path);
return null;
} }
var width = size.Width; return item.GetDefaultPrimaryImageAspectRatio();
var height = size.Height;
if (width <= 0 || height <= 0)
{
return null;
}
return (double)width / height;
} }
} }
} }
@@ -29,7 +29,7 @@
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="6.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.0" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.1" />
<PackageReference Include="Mono.Nat" Version="3.0.2" /> <PackageReference Include="Mono.Nat" Version="3.0.2" />
<PackageReference Include="prometheus-net.DotNetRuntime" Version="4.2.2" /> <PackageReference Include="prometheus-net.DotNetRuntime" Version="4.2.2" />
<PackageReference Include="sharpcompress" Version="0.30.1" /> <PackageReference Include="sharpcompress" Version="0.30.1" />
@@ -49,10 +49,14 @@
<NoWarn>AD0001</NoWarn> <NoWarn>AD0001</NoWarn>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
</PropertyGroup>
<!-- Code Analyzers--> <!-- Code Analyzers-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' "> <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" /> <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup> </ItemGroup>
@@ -13,7 +13,6 @@ using Jellyfin.Networking.Configuration;
using MediaBrowser.Controller; using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Plugins;
using MediaBrowser.Model.Dlna;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Mono.Nat; using Mono.Nat;
@@ -101,7 +101,7 @@ namespace Emby.Server.Implementations.EntryPoints
} }
} }
_lastProgressMessageTimes.AddOrUpdate(item.Id, key => DateTime.UtcNow, (key, existing) => DateTime.UtcNow); _lastProgressMessageTimes.AddOrUpdate(item.Id, _ => DateTime.UtcNow, (_, _) => DateTime.UtcNow);
var dict = new Dictionary<string, string>(); var dict = new Dictionary<string, string>();
dict["ItemId"] = item.Id.ToString("N", CultureInfo.InvariantCulture); dict["ItemId"] = item.Id.ToString("N", CultureInfo.InvariantCulture);
@@ -144,7 +144,7 @@ namespace Emby.Server.Implementations.EntryPoints
{ {
OnProviderRefreshProgress(sender, new GenericEventArgs<Tuple<BaseItem, double>>(new Tuple<BaseItem, double>(e.Argument, 100))); OnProviderRefreshProgress(sender, new GenericEventArgs<Tuple<BaseItem, double>>(new Tuple<BaseItem, double>(e.Argument, 100)));
_lastProgressMessageTimes.TryRemove(e.Argument.Id, out DateTime removed); _lastProgressMessageTimes.TryRemove(e.Argument.Id, out _);
} }
private static bool EnableRefreshMessage(BaseItem item) private static bool EnableRefreshMessage(BaseItem item)
@@ -423,7 +423,6 @@ namespace Emby.Server.Implementations.EntryPoints
continue; continue;
} }
var collectionFolders = _libraryManager.GetCollectionFolders(item, allUserRootChildren);
foreach (var folder in allUserRootChildren) foreach (var folder in allUserRootChildren)
{ {
list.Add(folder.Id.ToString("N", CultureInfo.InvariantCulture)); list.Add(folder.Id.ToString("N", CultureInfo.InvariantCulture));
@@ -465,6 +464,7 @@ namespace Emby.Server.Implementations.EntryPoints
public void Dispose() public void Dispose()
{ {
Dispose(true); Dispose(true);
GC.SuppressFinalize(this);
} }
/// <summary> /// <summary>
@@ -95,7 +95,7 @@ namespace Emby.Server.Implementations.EntryPoints
var changes = _changedItems.ToList(); var changes = _changedItems.ToList();
_changedItems.Clear(); _changedItems.Clear();
var task = SendNotifications(changes, CancellationToken.None); SendNotifications(changes, CancellationToken.None).GetAwaiter().GetResult();
if (_updateTimer != null) if (_updateTimer != null)
{ {
@@ -2,7 +2,6 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Authentication;
using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Net;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
@@ -47,7 +47,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
{ {
var session = await GetSession(requestContext).ConfigureAwait(false); var session = await GetSession(requestContext).ConfigureAwait(false);
return session == null || session.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(session.UserId); return session.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(session.UserId);
} }
public Task<User?> GetUser(object requestContext) public Task<User?> GetUser(object requestContext)
@@ -42,17 +42,14 @@ namespace Emby.Server.Implementations.HttpServer
/// <param name="logger">The logger.</param> /// <param name="logger">The logger.</param>
/// <param name="socket">The socket.</param> /// <param name="socket">The socket.</param>
/// <param name="remoteEndPoint">The remote end point.</param> /// <param name="remoteEndPoint">The remote end point.</param>
/// <param name="query">The query.</param>
public WebSocketConnection( public WebSocketConnection(
ILogger<WebSocketConnection> logger, ILogger<WebSocketConnection> logger,
WebSocket socket, WebSocket socket,
IPAddress? remoteEndPoint, IPAddress? remoteEndPoint)
IQueryCollection query)
{ {
_logger = logger; _logger = logger;
_socket = socket; _socket = socket;
RemoteEndPoint = remoteEndPoint; RemoteEndPoint = remoteEndPoint;
QueryString = query;
_jsonOptions = JsonDefaults.Options; _jsonOptions = JsonDefaults.Options;
LastActivityDate = DateTime.Now; LastActivityDate = DateTime.Now;
@@ -81,12 +78,6 @@ namespace Emby.Server.Implementations.HttpServer
/// <inheritdoc /> /// <inheritdoc />
public DateTime LastKeepAliveDate { get; set; } public DateTime LastKeepAliveDate { get; set; }
/// <summary>
/// Gets the query string.
/// </summary>
/// <value>The query string.</value>
public IQueryCollection QueryString { get; }
/// <summary> /// <summary>
/// Gets the state. /// Gets the state.
/// </summary> /// </summary>
@@ -180,7 +171,7 @@ namespace Emby.Server.Implementations.HttpServer
} }
WebSocketMessage<object>? stub; WebSocketMessage<object>? stub;
long bytesConsumed = 0; long bytesConsumed;
try try
{ {
stub = DeserializeWebSocketMessage(buffer, out bytesConsumed); stub = DeserializeWebSocketMessage(buffer, out bytesConsumed);
@@ -236,7 +227,8 @@ namespace Emby.Server.Implementations.HttpServer
{ {
MessageId = Guid.NewGuid(), MessageId = Guid.NewGuid(),
MessageType = SessionMessageType.KeepAlive MessageType = SessionMessageType.KeepAlive
}, CancellationToken.None); },
CancellationToken.None);
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -7,6 +7,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net.WebSockets; using System.Net.WebSockets;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Net;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@@ -50,8 +51,7 @@ namespace Emby.Server.Implementations.HttpServer
using var connection = new WebSocketConnection( using var connection = new WebSocketConnection(
_loggerFactory.CreateLogger<WebSocketConnection>(), _loggerFactory.CreateLogger<WebSocketConnection>(),
webSocket, webSocket,
context.Connection.RemoteIpAddress, context.GetNormalizedRemoteIp())
context.Request.Query)
{ {
OnReceive = ProcessWebSocketMessageReceived OnReceive = ProcessWebSocketMessageReceived
}; };
@@ -59,7 +59,7 @@ namespace Emby.Server.Implementations.HttpServer
var tasks = new Task[_webSocketListeners.Length]; var tasks = new Task[_webSocketListeners.Length];
for (var i = 0; i < _webSocketListeners.Length; ++i) for (var i = 0; i < _webSocketListeners.Length; ++i)
{ {
tasks[i] = _webSocketListeners[i].ProcessWebSocketConnectedAsync(connection); tasks[i] = _webSocketListeners[i].ProcessWebSocketConnectedAsync(connection, context);
} }
await Task.WhenAll(tasks).ConfigureAwait(false); await Task.WhenAll(tasks).ConfigureAwait(false);
@@ -217,8 +217,13 @@ namespace Emby.Server.Implementations.IO
/// <inheritdoc /> /// <inheritdoc />
public void Dispose() public void Dispose()
{ {
_disposed = true; if (_disposed)
{
return;
}
DisposeTimer(); DisposeTimer();
_disposed = true;
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
} }
} }
@@ -99,7 +99,7 @@ namespace Emby.Server.Implementations.IO
// But if we make this delay too high, we risk missing legitimate changes, such as user adding a new file, or hand-editing metadata // But if we make this delay too high, we risk missing legitimate changes, such as user adding a new file, or hand-editing metadata
await Task.Delay(45000).ConfigureAwait(false); await Task.Delay(45000).ConfigureAwait(false);
_tempIgnoredPaths.TryRemove(path, out var val); _tempIgnoredPaths.TryRemove(path, out _);
if (refreshPath) if (refreshPath)
{ {
@@ -544,16 +544,6 @@ namespace Emby.Server.Implementations.IO
/// <inheritdoc /> /// <inheritdoc />
public virtual bool AreEqual(string path1, string path2) public virtual bool AreEqual(string path1, string path2)
{ {
if (path1 == null && path2 == null)
{
return true;
}
if (path1 == null || path2 == null)
{
return false;
}
return string.Equals( return string.Equals(
NormalizePath(path1), NormalizePath(path1),
NormalizePath(path2), NormalizePath(path2),
+10 -10
View File
@@ -17,11 +17,11 @@ namespace Emby.Server.Implementations.IO
try try
{ {
int read; int read;
while ((read = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0) while ((read = await source.ReadAsync(buffer, cancellationToken).ConfigureAwait(false)) != 0)
{ {
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
await destination.WriteAsync(buffer, 0, read, cancellationToken).ConfigureAwait(false); await destination.WriteAsync(buffer.AsMemory(0, read), cancellationToken).ConfigureAwait(false);
if (onStarted != null) if (onStarted != null)
{ {
@@ -44,11 +44,11 @@ namespace Emby.Server.Implementations.IO
if (emptyReadLimit <= 0) if (emptyReadLimit <= 0)
{ {
int read; int read;
while ((read = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0) while ((read = await source.ReadAsync(buffer, cancellationToken).ConfigureAwait(false)) != 0)
{ {
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
await destination.WriteAsync(buffer, 0, read, cancellationToken).ConfigureAwait(false); await destination.WriteAsync(buffer.AsMemory(0, read), cancellationToken).ConfigureAwait(false);
} }
return; return;
@@ -60,7 +60,7 @@ namespace Emby.Server.Implementations.IO
{ {
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
var bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false); var bytesRead = await source.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
if (bytesRead == 0) if (bytesRead == 0)
{ {
@@ -71,7 +71,7 @@ namespace Emby.Server.Implementations.IO
{ {
eofCount = 0; eofCount = 0;
await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false); await destination.WriteAsync(buffer.AsMemory(0, bytesRead), cancellationToken).ConfigureAwait(false);
} }
} }
} }
@@ -88,13 +88,13 @@ namespace Emby.Server.Implementations.IO
{ {
int bytesRead; int bytesRead;
while ((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0) while ((bytesRead = await source.ReadAsync(buffer, cancellationToken).ConfigureAwait(false)) != 0)
{ {
var bytesToWrite = Math.Min(bytesRead, copyLength); var bytesToWrite = Math.Min(bytesRead, copyLength);
if (bytesToWrite > 0) if (bytesToWrite > 0)
{ {
await destination.WriteAsync(buffer, 0, Convert.ToInt32(bytesToWrite), cancellationToken).ConfigureAwait(false); await destination.WriteAsync(buffer.AsMemory(0, Convert.ToInt32(bytesToWrite)), cancellationToken).ConfigureAwait(false);
} }
copyLength -= bytesToWrite; copyLength -= bytesToWrite;
@@ -137,9 +137,9 @@ namespace Emby.Server.Implementations.IO
int bytesRead; int bytesRead;
int totalBytesRead = 0; int totalBytesRead = 0;
while ((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0) while ((bytesRead = await source.ReadAsync(buffer, cancellationToken).ConfigureAwait(false)) != 0)
{ {
await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false); await destination.WriteAsync(buffer.AsMemory(0, bytesRead), cancellationToken).ConfigureAwait(false);
totalBytesRead += bytesRead; totalBytesRead += bytesRead;
} }
@@ -28,35 +28,35 @@ namespace Emby.Server.Implementations.Images
var view = (CollectionFolder)item; var view = (CollectionFolder)item;
var viewType = view.CollectionType; var viewType = view.CollectionType;
string[] includeItemTypes; BaseItemKind[] includeItemTypes;
if (string.Equals(viewType, CollectionType.Movies, StringComparison.Ordinal)) if (string.Equals(viewType, CollectionType.Movies, StringComparison.Ordinal))
{ {
includeItemTypes = new string[] { "Movie" }; includeItemTypes = new[] { BaseItemKind.Movie };
} }
else if (string.Equals(viewType, CollectionType.TvShows, StringComparison.Ordinal)) else if (string.Equals(viewType, CollectionType.TvShows, StringComparison.Ordinal))
{ {
includeItemTypes = new string[] { "Series" }; includeItemTypes = new[] { BaseItemKind.Series };
} }
else if (string.Equals(viewType, CollectionType.Music, StringComparison.Ordinal)) else if (string.Equals(viewType, CollectionType.Music, StringComparison.Ordinal))
{ {
includeItemTypes = new string[] { "MusicAlbum" }; includeItemTypes = new[] { BaseItemKind.MusicAlbum };
} }
else if (string.Equals(viewType, CollectionType.Books, StringComparison.Ordinal)) else if (string.Equals(viewType, CollectionType.Books, StringComparison.Ordinal))
{ {
includeItemTypes = new string[] { "Book", "AudioBook" }; includeItemTypes = new[] { BaseItemKind.Book, BaseItemKind.AudioBook };
} }
else if (string.Equals(viewType, CollectionType.BoxSets, StringComparison.Ordinal)) else if (string.Equals(viewType, CollectionType.BoxSets, StringComparison.Ordinal))
{ {
includeItemTypes = new string[] { "BoxSet" }; includeItemTypes = new[] { BaseItemKind.BoxSet };
} }
else if (string.Equals(viewType, CollectionType.HomeVideos, StringComparison.Ordinal) || string.Equals(viewType, CollectionType.Photos, StringComparison.Ordinal)) else if (string.Equals(viewType, CollectionType.HomeVideos, StringComparison.Ordinal) || string.Equals(viewType, CollectionType.Photos, StringComparison.Ordinal))
{ {
includeItemTypes = new string[] { "Video", "Photo" }; includeItemTypes = new[] { BaseItemKind.Video, BaseItemKind.Photo };
} }
else else
{ {
includeItemTypes = new string[] { "Video", "Audio", "Photo", "Movie", "Series" }; includeItemTypes = new[] { BaseItemKind.Video, BaseItemKind.Audio, BaseItemKind.Photo, BaseItemKind.Movie, BaseItemKind.Series };
} }
var recursive = !string.Equals(CollectionType.Playlists, viewType, StringComparison.OrdinalIgnoreCase); var recursive = !string.Equals(CollectionType.Playlists, viewType, StringComparison.OrdinalIgnoreCase);
@@ -68,9 +68,9 @@ namespace Emby.Server.Implementations.Images
DtoOptions = new DtoOptions(false), DtoOptions = new DtoOptions(false),
ImageTypes = new ImageType[] { ImageType.Primary }, ImageTypes = new ImageType[] { ImageType.Primary },
Limit = 8, Limit = 8,
OrderBy = new ValueTuple<string, SortOrder>[] OrderBy = new[]
{ {
new ValueTuple<string, SortOrder>(ItemSortBy.Random, SortOrder.Ascending) (ItemSortBy.Random, SortOrder.Ascending)
}, },
IncludeItemTypes = includeItemTypes IncludeItemTypes = includeItemTypes
}); });
@@ -6,6 +6,8 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Dto;
@@ -34,14 +36,14 @@ namespace Emby.Server.Implementations.Images
var view = (UserView)item; var view = (UserView)item;
var isUsingCollectionStrip = IsUsingCollectionStrip(view); var isUsingCollectionStrip = IsUsingCollectionStrip(view);
var recursive = isUsingCollectionStrip && !new[] { CollectionType.BoxSets, CollectionType.Playlists }.Contains(view.ViewType ?? string.Empty, StringComparer.OrdinalIgnoreCase); var recursive = isUsingCollectionStrip && !new[] { CollectionType.BoxSets, CollectionType.Playlists }.Contains(view.ViewType ?? string.Empty, StringComparison.OrdinalIgnoreCase);
var result = view.GetItemList(new InternalItemsQuery var result = view.GetItemList(new InternalItemsQuery
{ {
User = view.UserId.HasValue ? _userManager.GetUserById(view.UserId.Value) : null, User = view.UserId.HasValue ? _userManager.GetUserById(view.UserId.Value) : null,
CollapseBoxSetItems = false, CollapseBoxSetItems = false,
Recursive = recursive, Recursive = recursive,
ExcludeItemTypes = new[] { "UserView", "CollectionFolder", "Person" }, ExcludeItemTypes = new[] { BaseItemKind.UserView, BaseItemKind.CollectionFolder, BaseItemKind.Person },
DtoOptions = new DtoOptions(false) DtoOptions = new DtoOptions(false)
}); });
@@ -8,8 +8,6 @@ using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
@@ -43,7 +41,7 @@ namespace Emby.Server.Implementations.Images
return _libraryManager.GetItemList(new InternalItemsQuery return _libraryManager.GetItemList(new InternalItemsQuery
{ {
Genres = new[] { item.Name }, Genres = new[] { item.Name },
IncludeItemTypes = new[] { nameof(Series), nameof(Movie) }, IncludeItemTypes = new[] { BaseItemKind.Series, BaseItemKind.Movie },
OrderBy = new[] { (ItemSortBy.Random, SortOrder.Ascending) }, OrderBy = new[] { (ItemSortBy.Random, SortOrder.Ascending) },
Limit = 4, Limit = 4,
Recursive = true, Recursive = true,
@@ -44,9 +44,9 @@ namespace Emby.Server.Implementations.Images
Genres = new[] { item.Name }, Genres = new[] { item.Name },
IncludeItemTypes = new[] IncludeItemTypes = new[]
{ {
nameof(MusicAlbum), BaseItemKind.MusicAlbum,
nameof(MusicVideo), BaseItemKind.MusicVideo,
nameof(Audio) BaseItemKind.Audio
}, },
OrderBy = new[] { (ItemSortBy.Random, SortOrder.Ascending) }, OrderBy = new[] { (ItemSortBy.Random, SortOrder.Ascending) },
Limit = 4, Limit = 4,
@@ -54,20 +54,10 @@ namespace Emby.Server.Implementations.Library
{ {
if (parent != null) if (parent != null)
{ {
// Ignore trailer folders but allow it at the collection level // Ignore extras folders but allow it at the collection level
if (string.Equals(filename, BaseItem.TrailersFolderName, StringComparison.OrdinalIgnoreCase) if (_namingOptions.AllExtrasTypesFolderNames.ContainsKey(filename)
&& !(parent is AggregateFolder) && parent is not AggregateFolder
&& !(parent is UserRootFolder)) && parent is not UserRootFolder)
{
return true;
}
if (string.Equals(filename, BaseItem.ThemeVideosFolderName, StringComparison.OrdinalIgnoreCase))
{
return true;
}
if (string.Equals(filename, BaseItem.ThemeSongsFolderName, StringComparison.OrdinalIgnoreCase))
{ {
return true; return true;
} }
@@ -11,14 +11,12 @@ using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Emby.Naming.Audio;
using Emby.Naming.Common; using Emby.Naming.Common;
using Emby.Naming.TV; using Emby.Naming.TV;
using Emby.Naming.Video;
using Emby.Server.Implementations.Library.Resolvers; using Emby.Server.Implementations.Library.Resolvers;
using Emby.Server.Implementations.Library.Validators; using Emby.Server.Implementations.Library.Validators;
using Emby.Server.Implementations.Playlists; using Emby.Server.Implementations.Playlists;
using Emby.Server.Implementations.ScheduledTasks; using Emby.Server.Implementations.ScheduledTasks.Tasks;
using Jellyfin.Data.Entities; using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
using Jellyfin.Extensions; using Jellyfin.Extensions;
@@ -80,6 +78,7 @@ namespace Emby.Server.Implementations.Library
private readonly IItemRepository _itemRepository; private readonly IItemRepository _itemRepository;
private readonly IImageProcessor _imageProcessor; private readonly IImageProcessor _imageProcessor;
private readonly NamingOptions _namingOptions; private readonly NamingOptions _namingOptions;
private readonly ExtraResolver _extraResolver;
/// <summary> /// <summary>
/// The _root folder sync lock. /// The _root folder sync lock.
@@ -148,6 +147,8 @@ namespace Emby.Server.Implementations.Library
_memoryCache = memoryCache; _memoryCache = memoryCache;
_namingOptions = namingOptions; _namingOptions = namingOptions;
_extraResolver = new ExtraResolver(namingOptions);
_configurationManager.ConfigurationUpdated += ConfigurationUpdated; _configurationManager.ConfigurationUpdated += ConfigurationUpdated;
RecordConfigurationValues(configurationManager.Configuration); RecordConfigurationValues(configurationManager.Configuration);
@@ -533,8 +534,8 @@ namespace Emby.Server.Implementations.Library
return key.GetMD5(); return key.GetMD5();
} }
public BaseItem ResolvePath(FileSystemMetadata fileInfo, Folder parent = null) public BaseItem ResolvePath(FileSystemMetadata fileInfo, Folder parent = null, IDirectoryService directoryService = null)
=> ResolvePath(fileInfo, new DirectoryService(_fileSystem), null, parent); => ResolvePath(fileInfo, directoryService ?? new DirectoryService(_fileSystem), null, parent);
private BaseItem ResolvePath( private BaseItem ResolvePath(
FileSystemMetadata fileInfo, FileSystemMetadata fileInfo,
@@ -654,7 +655,7 @@ namespace Emby.Server.Implementations.Library
return !args.ContainsFileSystemEntryByName(".ignore"); return !args.ContainsFileSystemEntryByName(".ignore");
} }
public IEnumerable<BaseItem> ResolvePaths(IEnumerable<FileSystemMetadata> files, IDirectoryService directoryService, Folder parent, LibraryOptions libraryOptions, string collectionType) public IEnumerable<BaseItem> ResolvePaths(IEnumerable<FileSystemMetadata> files, IDirectoryService directoryService, Folder parent, LibraryOptions libraryOptions, string collectionType = null)
{ {
return ResolvePaths(files, directoryService, parent, libraryOptions, collectionType, EntityResolvers); return ResolvePaths(files, directoryService, parent, libraryOptions, collectionType, EntityResolvers);
} }
@@ -677,7 +678,7 @@ namespace Emby.Server.Implementations.Library
{ {
var result = resolver.ResolveMultiple(parent, fileList, collectionType, directoryService); var result = resolver.ResolveMultiple(parent, fileList, collectionType, directoryService);
if (result != null && result.Items.Count > 0) if (result?.Items.Count > 0)
{ {
var items = new List<BaseItem>(); var items = new List<BaseItem>();
items.AddRange(result.Items); items.AddRange(result.Items);
@@ -965,7 +966,7 @@ namespace Emby.Server.Implementations.Library
{ {
var existing = GetItemList(new InternalItemsQuery var existing = GetItemList(new InternalItemsQuery
{ {
IncludeItemTypes = new[] { nameof(MusicArtist) }, IncludeItemTypes = new[] { BaseItemKind.MusicArtist },
Name = name, Name = name,
DtoOptions = options DtoOptions = options
}).Cast<MusicArtist>() }).Cast<MusicArtist>()
@@ -1375,7 +1376,7 @@ namespace Emby.Server.Implementations.Library
return _itemRepository.GetItemIdsList(query); return _itemRepository.GetItemIdsList(query);
} }
public QueryResult<(BaseItem, ItemCounts)> GetStudios(InternalItemsQuery query) public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetStudios(InternalItemsQuery query)
{ {
if (query.User != null) if (query.User != null)
{ {
@@ -1386,7 +1387,7 @@ namespace Emby.Server.Implementations.Library
return _itemRepository.GetStudios(query); return _itemRepository.GetStudios(query);
} }
public QueryResult<(BaseItem, ItemCounts)> GetGenres(InternalItemsQuery query) public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetGenres(InternalItemsQuery query)
{ {
if (query.User != null) if (query.User != null)
{ {
@@ -1397,7 +1398,7 @@ namespace Emby.Server.Implementations.Library
return _itemRepository.GetGenres(query); return _itemRepository.GetGenres(query);
} }
public QueryResult<(BaseItem, ItemCounts)> GetMusicGenres(InternalItemsQuery query) public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetMusicGenres(InternalItemsQuery query)
{ {
if (query.User != null) if (query.User != null)
{ {
@@ -1408,7 +1409,7 @@ namespace Emby.Server.Implementations.Library
return _itemRepository.GetMusicGenres(query); return _itemRepository.GetMusicGenres(query);
} }
public QueryResult<(BaseItem, ItemCounts)> GetAllArtists(InternalItemsQuery query) public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetAllArtists(InternalItemsQuery query)
{ {
if (query.User != null) if (query.User != null)
{ {
@@ -1419,7 +1420,7 @@ namespace Emby.Server.Implementations.Library
return _itemRepository.GetAllArtists(query); return _itemRepository.GetAllArtists(query);
} }
public QueryResult<(BaseItem, ItemCounts)> GetArtists(InternalItemsQuery query) public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetArtists(InternalItemsQuery query)
{ {
if (query.User != null) if (query.User != null)
{ {
@@ -1460,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) if (query.User != null)
{ {
@@ -1759,7 +1760,7 @@ namespace Emby.Server.Implementations.Library
return orderedItems ?? items; return orderedItems ?? items;
} }
public IEnumerable<BaseItem> Sort(IEnumerable<BaseItem> items, User user, IEnumerable<ValueTuple<string, SortOrder>> orderBy) public IEnumerable<BaseItem> Sort(IEnumerable<BaseItem> items, User user, IEnumerable<(string OrderBy, SortOrder SortOrder)> orderBy)
{ {
var isFirst = true; var isFirst = true;
@@ -2680,94 +2681,70 @@ namespace Emby.Server.Implementations.Library
return new ItemLookupInfo return new ItemLookupInfo
{ {
Name = VideoResolver.TryCleanString(result.Name, namingOptions, out var newName) ? newName.ToString() : result.Name, Name = VideoResolver.TryCleanString(result.Name, namingOptions, out var newName) ? newName : result.Name,
Year = result.Year Year = result.Year
}; };
} }
public IEnumerable<Video> FindTrailers(BaseItem owner, List<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService) public IEnumerable<BaseItem> FindExtras(BaseItem owner, List<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService)
{ {
var namingOptions = _namingOptions; var ownerVideoInfo = VideoResolver.Resolve(owner.Path, owner.IsFolder, _namingOptions);
if (ownerVideoInfo == null)
var files = owner.IsInMixedFolder ? new List<FileSystemMetadata>() : fileSystemChildren.Where(i => i.IsDirectory)
.Where(i => string.Equals(i.Name, BaseItem.TrailersFolderName, StringComparison.OrdinalIgnoreCase))
.SelectMany(i => _fileSystem.GetFiles(i.FullName, namingOptions.VideoFileExtensions, false, false))
.ToList();
var videos = VideoListResolver.Resolve(fileSystemChildren, namingOptions);
var currentVideo = videos.FirstOrDefault(i => string.Equals(owner.Path, i.Files[0].Path, StringComparison.OrdinalIgnoreCase));
if (currentVideo != null)
{ {
files.AddRange(currentVideo.Extras.Where(i => i.ExtraType == ExtraType.Trailer).Select(i => _fileSystem.GetFileInfo(i.Path))); yield break;
} }
var resolvers = new IItemResolver[] var count = fileSystemChildren.Count;
for (var i = 0; i < count; i++)
{ {
new GenericVideoResolver<Trailer>(_namingOptions) var current = fileSystemChildren[i];
}; if (current.IsDirectory && _namingOptions.AllExtrasTypesFolderNames.ContainsKey(current.Name))
return ResolvePaths(files, directoryService, null, new LibraryOptions(), null, resolvers)
.OfType<Trailer>()
.Select(video =>
{ {
// Try to retrieve it from the db. If we don't find it, use the resolved version var filesInSubFolder = _fileSystem.GetFiles(current.FullName, null, false, false);
if (GetItemById(video.Id) is Trailer dbItem) foreach (var file in filesInSubFolder)
{ {
video = dbItem; if (!_extraResolver.TryGetExtraTypeForOwner(file.FullName, ownerVideoInfo, out var extraType))
{
continue;
}
var extra = GetExtra(file, extraType.Value);
if (extra != null)
{
yield return extra;
}
} }
}
video.ParentId = Guid.Empty; else if (!current.IsDirectory && _extraResolver.TryGetExtraTypeForOwner(current.FullName, ownerVideoInfo, out var extraType))
video.OwnerId = owner.Id; {
video.ExtraType = ExtraType.Trailer; var extra = GetExtra(current, extraType.Value);
video.TrailerTypes = new[] { TrailerType.LocalTrailer }; if (extra != null)
{
return video; yield return extra;
}
// Sort them so that the list can be easily compared for changes }
}).OrderBy(i => i.Path);
}
public IEnumerable<Video> FindExtras(BaseItem owner, List<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService)
{
var namingOptions = _namingOptions;
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, namingOptions.VideoFileExtensions, false, false))
.ToList();
var videos = VideoListResolver.Resolve(fileSystemChildren, namingOptions);
var currentVideo = videos.FirstOrDefault(i => string.Equals(owner.Path, i.Files[0].Path, StringComparison.OrdinalIgnoreCase));
if (currentVideo != null)
{
files.AddRange(currentVideo.Extras.Where(i => i.ExtraType != ExtraType.Trailer).Select(i => _fileSystem.GetFileInfo(i.Path)));
} }
return ResolvePaths(files, directoryService, null, new LibraryOptions(), null) BaseItem GetExtra(FileSystemMetadata file, ExtraType extraType)
.OfType<Video>() {
.Select(video => var extra = ResolvePath(_fileSystem.GetFileInfo(file.FullName), directoryService, _extraResolver.GetResolversForExtraType(extraType));
if (extra is not Video && extra is not Audio)
{ {
// Try to retrieve it from the db. If we don't find it, use the resolved version return null;
var dbItem = GetItemById(video.Id) as Video; }
if (dbItem != null) // Try to retrieve it from the db. If we don't find it, use the resolved version
{ var itemById = GetItemById(extra.Id);
video = dbItem; if (itemById != null)
} {
extra = itemById;
}
video.ParentId = Guid.Empty; extra.ExtraType = extraType;
video.OwnerId = owner.Id; extra.ParentId = Guid.Empty;
extra.OwnerId = owner.Id;
SetExtraTypeFromFilename(video); return extra;
}
return video;
// Sort them so that the list can be easily compared for changes
}).OrderBy(i => i.Path);
} }
public string GetPathAfterNetworkSubstitution(string path, BaseItem ownerItem) public string GetPathAfterNetworkSubstitution(string path, BaseItem ownerItem)
@@ -2817,15 +2794,6 @@ namespace Emby.Server.Implementations.Library
return path; return path;
} }
private void SetExtraTypeFromFilename(Video item)
{
var resolver = new ExtraResolver(_namingOptions);
var result = resolver.GetExtraInfo(item.Path);
item.ExtraType = result.ExtraType;
}
public List<PersonInfo> GetPeople(InternalPeopleQuery query) public List<PersonInfo> GetPeople(InternalPeopleQuery query)
{ {
return _itemRepository.GetPeople(query); return _itemRepository.GetPeople(query);
@@ -2932,11 +2900,12 @@ namespace Emby.Server.Implementations.Library
var rootFolderPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath; var rootFolderPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath;
var existingNameCount = 1; // first numbered name will be 2
var virtualFolderPath = Path.Combine(rootFolderPath, name); var virtualFolderPath = Path.Combine(rootFolderPath, name);
while (Directory.Exists(virtualFolderPath)) while (Directory.Exists(virtualFolderPath))
{ {
name += "1"; existingNameCount++;
virtualFolderPath = Path.Combine(rootFolderPath, name); virtualFolderPath = Path.Combine(rootFolderPath, name + " " + existingNameCount);
} }
var mediaPathInfos = options.PathInfos; var mediaPathInfos = options.PathInfos;
@@ -66,11 +66,8 @@ namespace Emby.Server.Implementations.Library
{ {
var delayMs = mediaSource.AnalyzeDurationMs ?? 0; var delayMs = mediaSource.AnalyzeDurationMs ?? 0;
delayMs = Math.Max(3000, delayMs); delayMs = Math.Max(3000, delayMs);
if (delayMs > 0) _logger.LogInformation("Waiting {0}ms before probing the live stream", delayMs);
{ await Task.Delay(delayMs, cancellationToken).ConfigureAwait(false);
_logger.LogInformation("Waiting {0}ms before probing the live stream", delayMs);
await Task.Delay(delayMs, cancellationToken).ConfigureAwait(false);
}
} }
mediaSource.AnalyzeDurationMs = 3000; mediaSource.AnalyzeDurationMs = 3000;
@@ -464,12 +464,11 @@ namespace Emby.Server.Implementations.Library
try try
{ {
var tuple = GetProvider(request.OpenToken); var (provider, keyId) = GetProvider(request.OpenToken);
var provider = tuple.Item1;
var currentLiveStreams = _openStreams.Values.ToList(); var currentLiveStreams = _openStreams.Values.ToList();
liveStream = await provider.OpenMediaSource(tuple.Item2, currentLiveStreams, cancellationToken).ConfigureAwait(false); liveStream = await provider.OpenMediaSource(keyId, currentLiveStreams, cancellationToken).ConfigureAwait(false);
mediaSource = liveStream.MediaSource; mediaSource = liveStream.MediaSource;
@@ -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)) if (string.IsNullOrEmpty(key))
{ {
@@ -6,6 +6,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
namespace Emby.Server.Implementations.Library namespace Emby.Server.Implementations.Library
@@ -64,18 +65,18 @@ namespace Emby.Server.Implementations.Library
stream = sortedStreams.FirstOrDefault(s => s.IsExternal || s.IsForced || s.IsDefault); stream = sortedStreams.FirstOrDefault(s => s.IsExternal || s.IsForced || s.IsDefault);
// if the audio language is not understood by the user, load their preferred subs, if there are any // if the audio language is not understood by the user, load their preferred subs, if there are any
if (stream == null && !preferredLanguages.Contains(audioTrackLanguage, StringComparer.OrdinalIgnoreCase)) if (stream == null && !preferredLanguages.Contains(audioTrackLanguage, StringComparison.OrdinalIgnoreCase))
{ {
stream = sortedStreams.FirstOrDefault(s => !s.IsForced && preferredLanguages.Contains(s.Language, StringComparer.OrdinalIgnoreCase)); stream = sortedStreams.FirstOrDefault(s => !s.IsForced && preferredLanguages.Contains(s.Language, StringComparison.OrdinalIgnoreCase));
} }
} }
else if (mode == SubtitlePlaybackMode.Smart) else if (mode == SubtitlePlaybackMode.Smart)
{ {
// if the audio language is not understood by the user, load their preferred subs, if there are any // if the audio language is not understood by the user, load their preferred subs, if there are any
if (!preferredLanguages.Contains(audioTrackLanguage, StringComparer.OrdinalIgnoreCase)) if (!preferredLanguages.Contains(audioTrackLanguage, StringComparison.OrdinalIgnoreCase))
{ {
stream = streams.FirstOrDefault(s => !s.IsForced && preferredLanguages.Contains(s.Language, StringComparer.OrdinalIgnoreCase)) ?? stream = streams.FirstOrDefault(s => !s.IsForced && preferredLanguages.Contains(s.Language, StringComparison.OrdinalIgnoreCase)) ??
streams.FirstOrDefault(s => preferredLanguages.Contains(s.Language, StringComparer.OrdinalIgnoreCase)); streams.FirstOrDefault(s => preferredLanguages.Contains(s.Language, StringComparison.OrdinalIgnoreCase));
} }
} }
else if (mode == SubtitlePlaybackMode.Always) else if (mode == SubtitlePlaybackMode.Always)
@@ -136,9 +137,9 @@ namespace Emby.Server.Implementations.Library
else if (mode == SubtitlePlaybackMode.Smart) else if (mode == SubtitlePlaybackMode.Smart)
{ {
// Prefer smart logic over embedded metadata // Prefer smart logic over embedded metadata
if (!preferredLanguages.Contains(audioTrackLanguage, StringComparer.OrdinalIgnoreCase)) if (!preferredLanguages.Contains(audioTrackLanguage, StringComparison.OrdinalIgnoreCase))
{ {
filteredStreams = streams.Where(s => !s.IsForced && preferredLanguages.Contains(s.Language, StringComparer.OrdinalIgnoreCase)) filteredStreams = streams.Where(s => !s.IsForced && preferredLanguages.Contains(s.Language, StringComparison.OrdinalIgnoreCase))
.ToList(); .ToList();
} }
} }
@@ -52,7 +52,7 @@ namespace Emby.Server.Implementations.Library
var genres = item var genres = item
.GetRecursiveChildren(user, new InternalItemsQuery(user) .GetRecursiveChildren(user, new InternalItemsQuery(user)
{ {
IncludeItemTypes = new[] { nameof(Audio) }, IncludeItemTypes = new[] { BaseItemKind.Audio },
DtoOptions = dtoOptions DtoOptions = dtoOptions
}) })
.Cast<Audio>() .Cast<Audio>()
@@ -89,7 +89,7 @@ namespace Emby.Server.Implementations.Library
{ {
return _libraryManager.GetItemList(new InternalItemsQuery(user) return _libraryManager.GetItemList(new InternalItemsQuery(user)
{ {
IncludeItemTypes = new[] { nameof(Audio) }, IncludeItemTypes = new[] { BaseItemKind.Audio },
GenreIds = genreIds.ToArray(), GenreIds = genreIds.ToArray(),
@@ -103,7 +103,7 @@ namespace Emby.Server.Implementations.Library
public List<BaseItem> GetInstantMixFromItem(BaseItem item, User user, DtoOptions dtoOptions) public List<BaseItem> GetInstantMixFromItem(BaseItem item, User user, DtoOptions dtoOptions)
{ {
if (item is MusicGenre genre) if (item is MusicGenre)
{ {
return GetInstantMixFromGenreIds(new[] { item.Id }, user, dtoOptions); return GetInstantMixFromGenreIds(new[] { item.Id }, user, dtoOptions);
} }
@@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.Library
/// <param name="attribute">The attrib.</param> /// <param name="attribute">The attrib.</param>
/// <returns>System.String.</returns> /// <returns>System.String.</returns>
/// <exception cref="ArgumentException"><paramref name="str" /> or <paramref name="attribute" /> is empty.</exception> /// <exception cref="ArgumentException"><paramref name="str" /> or <paramref name="attribute" /> is empty.</exception>
public static string? GetAttributeValue(this string str, string attribute) public static string? GetAttributeValue(this ReadOnlySpan<char> str, ReadOnlySpan<char> attribute)
{ {
if (str.Length == 0) if (str.Length == 0)
{ {
@@ -28,17 +28,31 @@ namespace Emby.Server.Implementations.Library
throw new ArgumentException("String can't be empty.", nameof(attribute)); throw new ArgumentException("String can't be empty.", nameof(attribute));
} }
string srch = "[" + attribute + "="; var attributeIndex = str.IndexOf(attribute, StringComparison.OrdinalIgnoreCase);
int start = str.IndexOf(srch, StringComparison.OrdinalIgnoreCase);
if (start != -1) // Must be at least 3 characters after the attribute =, ], any character.
var maxIndex = str.Length - attribute.Length - 3;
while (attributeIndex > -1 && attributeIndex < maxIndex)
{ {
start += srch.Length; var attributeEnd = attributeIndex + attribute.Length;
int end = str.IndexOf(']', start); if (attributeIndex > 0
return str.Substring(start, end - start); && str[attributeIndex - 1] == '['
&& (str[attributeEnd] == '=' || str[attributeEnd] == '-'))
{
var closingIndex = str[attributeEnd..].IndexOf(']');
// Must be at least 1 character before the closing bracket.
if (closingIndex > 1)
{
return str[(attributeEnd + 1)..(attributeEnd + closingIndex)].Trim().ToString();
}
}
str = str[attributeEnd..];
attributeIndex = str.IndexOf(attribute, StringComparison.OrdinalIgnoreCase);
} }
// for imdbid we also accept pattern matching // for imdbid we also accept pattern matching
if (string.Equals(attribute, "imdbid", StringComparison.OrdinalIgnoreCase)) if (attribute.Equals("imdbid", StringComparison.OrdinalIgnoreCase))
{ {
var match = ProviderIdParsers.TryFindImdbId(str, out var imdbId); var match = ProviderIdParsers.TryFindImdbId(str, out var imdbId);
return match ? imdbId.ToString() : null; return match ? imdbId.ToString() : null;
@@ -112,7 +112,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
/// Determine if the supplied list contains what we should consider music. /// Determine if the supplied list contains what we should consider music.
/// </summary> /// </summary>
private bool ContainsMusic( private bool ContainsMusic(
IEnumerable<FileSystemMetadata> list, ICollection<FileSystemMetadata> list,
bool allowSubfolders, bool allowSubfolders,
IDirectoryService directoryService) IDirectoryService directoryService)
{ {
@@ -4,12 +4,10 @@ using System;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Emby.Naming.Common; using Emby.Naming.Common;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Resolvers; using MediaBrowser.Controller.Resolvers;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Library.Resolvers.Audio namespace Emby.Server.Implementations.Library.Resolvers.Audio
@@ -49,120 +49,71 @@ namespace Emby.Server.Implementations.Library.Resolvers
protected virtual TVideoType ResolveVideo<TVideoType>(ItemResolveArgs args, bool parseName) protected virtual TVideoType ResolveVideo<TVideoType>(ItemResolveArgs args, bool parseName)
where TVideoType : Video, new() where TVideoType : Video, new()
{ {
var namingOptions = NamingOptions; VideoFileInfo videoInfo = null;
VideoType? videoType = null;
// If the path is a file check for a matching extensions // If the path is a file check for a matching extensions
if (args.IsDirectory) if (args.IsDirectory)
{ {
TVideoType video = null;
VideoFileInfo videoInfo = null;
// Loop through each child file/folder and see if we find a video // Loop through each child file/folder and see if we find a video
foreach (var child in args.FileSystemChildren) foreach (var child in args.FileSystemChildren)
{ {
var filename = child.Name; var filename = child.Name;
if (child.IsDirectory) if (child.IsDirectory)
{ {
if (IsDvdDirectory(child.FullName, filename, args.DirectoryService)) if (IsDvdDirectory(child.FullName, filename, args.DirectoryService))
{ {
videoInfo = VideoResolver.ResolveDirectory(args.Path, namingOptions); videoType = VideoType.Dvd;
if (videoInfo == null)
{
return null;
}
video = new TVideoType
{
Path = args.Path,
VideoType = VideoType.Dvd,
ProductionYear = videoInfo.Year
};
break;
} }
else if (IsBluRayDirectory(filename))
if (IsBluRayDirectory(filename))
{ {
videoInfo = VideoResolver.ResolveDirectory(args.Path, namingOptions); videoType = VideoType.BluRay;
if (videoInfo == null)
{
return null;
}
video = new TVideoType
{
Path = args.Path,
VideoType = VideoType.BluRay,
ProductionYear = videoInfo.Year
};
break;
} }
} }
else if (IsDvdFile(filename)) else if (IsDvdFile(filename))
{ {
videoInfo = VideoResolver.ResolveDirectory(args.Path, namingOptions); videoType = VideoType.Dvd;
if (videoInfo == null)
{
return null;
}
video = new TVideoType
{
Path = args.Path,
VideoType = VideoType.Dvd,
ProductionYear = videoInfo.Year
};
break;
} }
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 else
{ {
var videoInfo = VideoResolver.Resolve(args.Path, false, namingOptions, false); videoInfo = VideoResolver.Resolve(args.Path, false, NamingOptions, parseName);
if (videoInfo == null)
{
return null;
}
if (VideoResolver.IsVideoFile(args.Path, NamingOptions) || 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;
}
} }
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) protected void SetVideoType(Video video, VideoFileInfo videoInfo)
@@ -207,8 +158,8 @@ namespace Emby.Server.Implementations.Library.Resolvers
{ {
// use disc-utils, both DVDs and BDs use UDF filesystem // use disc-utils, both DVDs and BDs use UDF filesystem
using (var videoFileStream = File.Open(video.Path, FileMode.Open, FileAccess.Read)) 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")) if (udfReader.DirectoryExists("VIDEO_TS"))
{ {
video.IsoType = IsoType.Dvd; video.IsoType = IsoType.Dvd;
@@ -5,6 +5,7 @@
using System; using System;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
@@ -32,7 +33,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Books
var extension = Path.GetExtension(args.Path); 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 // It's a book
return new Book return new Book
@@ -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);
}
}
}
@@ -1,15 +1,21 @@
#nullable disable #nullable disable
#pragma warning disable CS1591
using Emby.Naming.Common; using Emby.Naming.Common;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
namespace Emby.Server.Implementations.Library.Resolvers 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> public class GenericVideoResolver<T> : BaseVideoResolver<T>
where T : Video, new() where T : Video, new()
{ {
/// <summary>
/// Initializes a new instance of the <see cref="GenericVideoResolver{T}"/> class.
/// </summary>
/// <param name="namingOptions">The naming options.</param>
public GenericVideoResolver(NamingOptions namingOptions) public GenericVideoResolver(NamingOptions namingOptions)
: base(namingOptions) : base(namingOptions)
{ {
@@ -65,7 +65,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
private static void SetProviderIdFromPath(BaseItem item) private static void SetProviderIdFromPath(BaseItem item)
{ {
// we need to only look at the name of this actual item (not parents) // 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"); var id = justName.GetAttributeValue("tmdbid");
@@ -26,7 +26,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
public class MovieResolver : BaseVideoResolver<Video>, IMultiItemResolver public class MovieResolver : BaseVideoResolver<Video>, IMultiItemResolver
{ {
private readonly IImageProcessor _imageProcessor; private readonly IImageProcessor _imageProcessor;
private readonly StackResolver _stackResolver;
private string[] _validCollectionTypes = new[] private string[] _validCollectionTypes = new[]
{ {
@@ -46,7 +45,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
: base(namingOptions) : base(namingOptions)
{ {
_imageProcessor = imageProcessor; _imageProcessor = imageProcessor;
_stackResolver = new StackResolver(NamingOptions);
} }
/// <summary> /// <summary>
@@ -62,7 +60,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
string collectionType, string collectionType,
IDirectoryService directoryService) IDirectoryService directoryService)
{ {
var result = ResolveMultipleInternal(parent, files, collectionType, directoryService); var result = ResolveMultipleInternal(parent, files, collectionType);
if (result != null) if (result != null)
{ {
@@ -92,24 +90,24 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
return null; return null;
} }
Video movie = null;
var files = args.GetActualFileSystemChildren().ToList(); var files = args.GetActualFileSystemChildren().ToList();
if (string.Equals(collectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase)) 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)) 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)) 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) if (args.Parent == null)
{ {
// return FindMovie<Video>(args.Path, args.Parent, files, args.DirectoryService, collectionType);
return null; return null;
} }
@@ -118,23 +116,22 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
return null; return null;
} }
{ movie = FindMovie<Movie>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, true);
return FindMovie<Movie>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, true);
}
} }
if (string.Equals(collectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase)) 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) if (args.Parent == null)
{ {
return base.Resolve(args); return null;
} }
if (IsInvalid(args.Parent, collectionType)) if (IsInvalid(args.Parent, collectionType))
@@ -169,6 +166,12 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
item = ResolveVideo<Video>(args, false); item = ResolveVideo<Video>(args, false);
} }
// Ignore extras
if (item?.ExtraType != null)
{
return null;
}
if (item != null) if (item != null)
{ {
item.IsInMixedFolder = true; item.IsInMixedFolder = true;
@@ -180,8 +183,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
private MultiItemResolverResult ResolveMultipleInternal( private MultiItemResolverResult ResolveMultipleInternal(
Folder parent, Folder parent,
List<FileSystemMetadata> files, List<FileSystemMetadata> files,
string collectionType, string collectionType)
IDirectoryService directoryService)
{ {
if (IsInvalid(parent, collectionType)) if (IsInvalid(parent, collectionType))
{ {
@@ -190,13 +192,13 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
if (string.Equals(collectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase)) 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) || if (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase) ||
string.Equals(collectionType, CollectionType.Photos, 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)) if (string.IsNullOrEmpty(collectionType))
@@ -204,7 +206,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
// Owned items should just use the plain video type // Owned items should just use the plain video type
if (parent == null) 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()) if (parent is Series || parent.GetParents().OfType<Series>().Any())
@@ -212,12 +214,12 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
return null; 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)) 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; return null;
@@ -226,21 +228,20 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
private MultiItemResolverResult ResolveVideos<T>( private MultiItemResolverResult ResolveVideos<T>(
Folder parent, Folder parent,
IEnumerable<FileSystemMetadata> fileSystemEntries, IEnumerable<FileSystemMetadata> fileSystemEntries,
IDirectoryService directoryService, bool supportMultiEditions,
bool suppportMultiEditions,
string collectionType, string collectionType,
bool parseName) bool parseName)
where T : Video, new() where T : Video, new()
{ {
var files = new List<FileSystemMetadata>(); var files = new List<FileSystemMetadata>();
var videos = new List<BaseItem>();
var leftOver = new List<FileSystemMetadata>(); var leftOver = new List<FileSystemMetadata>();
var hasCollectionType = !string.IsNullOrEmpty(collectionType);
// Loop through each child file/folder and see if we find a video // Loop through each child file/folder and see if we find a video
foreach (var child in fileSystemEntries) foreach (var child in fileSystemEntries)
{ {
// This is a hack but currently no better way to resolve a sometimes ambiguous situation // 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) if (string.Equals(child.Name, "tvshow.nfo", StringComparison.OrdinalIgnoreCase)
|| string.Equals(child.Name, "season.nfo", StringComparison.OrdinalIgnoreCase)) || string.Equals(child.Name, "season.nfo", StringComparison.OrdinalIgnoreCase))
@@ -259,29 +260,39 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
} }
} }
var resolverResult = VideoListResolver.Resolve(files, NamingOptions, suppportMultiEditions).ToList(); var videoInfos = files
.Select(i => VideoResolver.Resolve(i.FullName, i.IsDirectory, NamingOptions, parseName))
.Where(f => f != null)
.ToList();
var resolverResult = VideoListResolver.Resolve(videoInfos, NamingOptions, supportMultiEditions, parseName);
var result = new MultiItemResolverResult var result = new MultiItemResolverResult
{ {
ExtraFiles = leftOver, ExtraFiles = leftOver
Items = videos
}; };
var isInMixedFolder = resolverResult.Count > 1 || (parent != null && parent.IsTopParent); var isInMixedFolder = resolverResult.Count > 1 || parent?.IsTopParent == true;
foreach (var video in resolverResult) foreach (var video in resolverResult)
{ {
var firstVideo = video.Files[0]; 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 var videoItem = new T
{ {
Path = video.Files[0].Path, Path = path,
IsInMixedFolder = isInMixedFolder, IsInMixedFolder = isInMixedFolder,
ProductionYear = video.Year, ProductionYear = video.Year,
Name = parseName ? Name = parseName ? video.Name : firstVideo.Name,
video.Name : AdditionalParts = additionalParts,
Path.GetFileNameWithoutExtension(video.Files[0].Path),
AdditionalParts = video.Files.Skip(1).Select(i => i.Path).ToArray(),
LocalAlternateVersions = video.AlternateVersions.Select(i => i.Path).ToArray() LocalAlternateVersions = video.AlternateVersions.Select(i => i.Path).ToArray()
}; };
@@ -299,21 +310,34 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
private static bool IsIgnored(string filename) private static bool IsIgnored(string filename)
{ {
// Ignore samples // 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; 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) for (var j = 0; j < current.AlternateVersions.Count; j++)
{ {
return result.Files.Any(i => ContainsFile(i, file)) || if (ContainsFile(current.AlternateVersions[j], file))
result.AlternateVersions.Any(i => ContainsFile(i, file)) || {
result.Extras.Any(i => ContainsFile(i, file)); return true;
}
}
}
return false;
} }
private static bool ContainsFile(VideoFileInfo result, FileSystemMetadata file) private static bool ContainsFile(VideoFileInfo result, FileSystemMetadata file)
@@ -342,9 +366,9 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
if (item is Movie || item is MusicVideo) if (item is Movie || item is MusicVideo)
{ {
// We need to only look at the name of this actual item (not parents) // 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 // check for tmdb id
var tmdbid = justName.GetAttributeValue("tmdbid"); var tmdbid = justName.GetAttributeValue("tmdbid");
@@ -358,7 +382,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
if (!string.IsNullOrEmpty(item.Path)) 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) // 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)) if (!string.IsNullOrWhiteSpace(imdbid))
{ {
@@ -431,7 +455,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
// TODO: Allow GetMultiDiscMovie in here // TODO: Allow GetMultiDiscMovie in here
const bool SupportsMultiVersion = true; 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(); new MultiItemResolverResult();
if (result.Items.Count == 1) if (result.Items.Count == 1)
@@ -510,7 +534,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
return null; return null;
} }
var result = _stackResolver.ResolveDirectories(folderPaths).ToList(); var result = StackResolver.ResolveDirectories(folderPaths, NamingOptions).ToList();
if (result.Count != 1) if (result.Count != 1)
{ {
@@ -8,6 +8,7 @@ using System.IO;
using System.Linq; using System.Linq;
using Emby.Naming.Common; using Emby.Naming.Common;
using Emby.Naming.Video; using Emby.Naming.Video;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
@@ -33,7 +34,6 @@ namespace Emby.Server.Implementations.Library.Resolvers
"default" "default"
}; };
public PhotoResolver(IImageProcessor imageProcessor, NamingOptions namingOptions) public PhotoResolver(IImageProcessor imageProcessor, NamingOptions namingOptions)
{ {
_imageProcessor = imageProcessor; _imageProcessor = imageProcessor;
@@ -109,7 +109,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
} }
string extension = Path.GetExtension(path).TrimStart('.'); string extension = Path.GetExtension(path).TrimStart('.');
return imageProcessor.SupportedInputFormats.Contains(extension, StringComparer.OrdinalIgnoreCase); return imageProcessor.SupportedInputFormats.Contains(extension, StringComparison.OrdinalIgnoreCase);
} }
} }
} }
@@ -5,6 +5,7 @@
using System; using System;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Playlists; using MediaBrowser.Controller.Playlists;
using MediaBrowser.Controller.Resolvers; using MediaBrowser.Controller.Resolvers;
@@ -57,10 +58,10 @@ namespace Emby.Server.Implementations.Library.Resolvers
// Check if this is a music playlist file // Check if this is a music playlist file
// It should have the correct collection type and a supported file extension // 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); 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 return new Playlist
{ {
@@ -3,7 +3,6 @@
using System; using System;
using System.Linq; using System.Linq;
using Emby.Naming.Common; using Emby.Naming.Common;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
@@ -45,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 // 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 // Also handle flat tv folders
if ((season != null || if (season != null ||
string.Equals(args.GetCollectionType(), CollectionType.TvShows, StringComparison.OrdinalIgnoreCase) || string.Equals(args.GetCollectionType(), CollectionType.TvShows, StringComparison.OrdinalIgnoreCase) ||
args.HasParent<Series>()) args.HasParent<Series>())
&& (parent is Series || !BaseItem.AllExtrasTypesFolderNames.ContainsKey(parent.Name)))
{ {
var episode = ResolveVideo<Episode>(args, false); 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) var series = parent as Series ?? parent.GetParents().OfType<Series>().FirstOrDefault();
{
episode.SeriesId = series.Id;
episode.SeriesName = series.Name;
}
if (season != null) if (series != null)
{ {
episode.SeasonId = season.Id; episode.SeriesId = series.Id;
episode.SeasonName = season.Name; episode.SeriesName = series.Name;
} }
// Assume season 1 if there's no season folder and a season number could not be determined if (season != null)
if (season == null && !episode.ParentIndexNumber.HasValue && (episode.IndexNumber.HasValue || episode.PremiereDate.HasValue)) {
{ episode.SeasonId = season.Id;
episode.ParentIndexNumber = 1; 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; return episode;
@@ -5,12 +5,9 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using Emby.Naming.Common; using Emby.Naming.Common;
using Emby.Naming.TV; using Emby.Naming.TV;
using Emby.Naming.Video; using Emby.Naming.Video;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Resolvers; using MediaBrowser.Controller.Resolvers;
@@ -185,13 +182,42 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
/// <param name="path">The path.</param> /// <param name="path">The path.</param>
private static void SetProviderIdFromPath(Series item, string path) private static void SetProviderIdFromPath(Series item, string path)
{ {
var justName = Path.GetFileName(path); var justName = Path.GetFileName(path.AsSpan());
var id = justName.GetAttributeValue("tvdbid"); var tvdbId = justName.GetAttributeValue("tvdbid");
if (!string.IsNullOrEmpty(tvdbId))
if (!string.IsNullOrEmpty(id))
{ {
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);
} }
} }
} }
@@ -10,12 +10,9 @@ using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Querying; using MediaBrowser.Model.Querying;
using MediaBrowser.Model.Search; using MediaBrowser.Model.Search;
using Genre = MediaBrowser.Controller.Entities.Genre;
using Person = MediaBrowser.Controller.Entities.Person;
namespace Emby.Server.Implementations.Library 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); list.Add(value);
} }
@@ -86,63 +83,63 @@ namespace Emby.Server.Implementations.Library
searchTerm = searchTerm.Trim().RemoveDiacritics(); searchTerm = searchTerm.Trim().RemoveDiacritics();
var excludeItemTypes = query.ExcludeItemTypes.ToList(); 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(BaseItemKind.Year);
excludeItemTypes.Add(nameof(Folder)); 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) if (!query.IncludeMedia)
{ {
AddIfMissing(includeItemTypes, nameof(Genre)); AddIfMissing(includeItemTypes, BaseItemKind.Genre);
AddIfMissing(includeItemTypes, nameof(MusicGenre)); AddIfMissing(includeItemTypes, BaseItemKind.MusicGenre);
} }
} }
else else
{ {
AddIfMissing(excludeItemTypes, nameof(Genre)); AddIfMissing(excludeItemTypes, BaseItemKind.Genre);
AddIfMissing(excludeItemTypes, nameof(MusicGenre)); 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) if (!query.IncludeMedia)
{ {
AddIfMissing(includeItemTypes, nameof(Person)); AddIfMissing(includeItemTypes, BaseItemKind.Person);
} }
} }
else 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) if (!query.IncludeMedia)
{ {
AddIfMissing(includeItemTypes, nameof(Studio)); AddIfMissing(includeItemTypes, BaseItemKind.Studio);
} }
} }
else 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) if (!query.IncludeMedia)
{ {
AddIfMissing(includeItemTypes, nameof(MusicArtist)); AddIfMissing(includeItemTypes, BaseItemKind.MusicArtist);
} }
} }
else else
{ {
AddIfMissing(excludeItemTypes, nameof(MusicArtist)); AddIfMissing(excludeItemTypes, BaseItemKind.MusicArtist);
} }
AddIfMissing(excludeItemTypes, nameof(CollectionFolder)); AddIfMissing(excludeItemTypes, BaseItemKind.CollectionFolder);
AddIfMissing(excludeItemTypes, nameof(Folder)); AddIfMissing(excludeItemTypes, BaseItemKind.Folder);
var mediaTypes = query.MediaTypes.ToList(); var mediaTypes = query.MediaTypes.ToList();
if (includeItemTypes.Count > 0) if (includeItemTypes.Count > 0)
@@ -183,7 +180,7 @@ namespace Emby.Server.Implementations.Library
List<BaseItem> mediaItems; 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)) if (!searchQuery.ParentId.Equals(Guid.Empty))
{ {
@@ -192,8 +189,8 @@ namespace Emby.Server.Implementations.Library
searchQuery.ParentId = Guid.Empty; searchQuery.ParentId = Guid.Empty;
searchQuery.IncludeItemsByName = true; searchQuery.IncludeItemsByName = true;
searchQuery.IncludeItemTypes = Array.Empty<string>(); searchQuery.IncludeItemTypes = Array.Empty<BaseItemKind>();
mediaItems = _libraryManager.GetAllArtists(searchQuery).Items.Select(i => i.Item1).ToList(); mediaItems = _libraryManager.GetAllArtists(searchQuery).Items.Select(i => i.Item).ToList();
} }
else else
{ {
@@ -75,7 +75,7 @@ namespace Emby.Server.Implementations.Library
} }
var cacheKey = GetCacheKey(userId, item.Id); var cacheKey = GetCacheKey(userId, item.Id);
_userData.AddOrUpdate(cacheKey, userData, (k, v) => userData); _userData.AddOrUpdate(cacheKey, userData, (_, _) => userData);
UserDataSaved?.Invoke(this, new UserDataSaveEventArgs UserDataSaved?.Invoke(this, new UserDataSaveEventArgs
{ {
@@ -125,7 +125,7 @@ namespace Emby.Server.Implementations.Library
var cacheKey = GetCacheKey(userId, itemId); 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) private UserItemData GetUserDataInternal(long internalUserId, List<string> keys)
@@ -8,11 +8,11 @@ using System.Linq;
using System.Threading; using System.Threading;
using Jellyfin.Data.Entities; using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Model.Channels; using MediaBrowser.Model.Channels;
@@ -20,8 +20,6 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Library; using MediaBrowser.Model.Library;
using MediaBrowser.Model.Querying; using MediaBrowser.Model.Querying;
using Genre = MediaBrowser.Controller.Entities.Genre;
using Person = MediaBrowser.Controller.Entities.Person;
namespace Emby.Server.Implementations.Library namespace Emby.Server.Implementations.Library
{ {
@@ -80,7 +78,7 @@ namespace Emby.Server.Implementations.Library
continue; 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)); list.Add(GetUserView(folder, folderViewType, string.Empty));
} }
@@ -175,12 +173,12 @@ namespace Emby.Server.Implementations.Library
string viewType, string viewType,
string localizationKey, string localizationKey,
string sortName, string sortName,
Jellyfin.Data.Entities.User user, User user,
string[] presetViews) string[] presetViews)
{ {
if (parents.Count == 1 && parents.All(i => string.Equals(i.CollectionType, viewType, StringComparison.OrdinalIgnoreCase))) 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]; 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))) 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))) 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 var excludeItemTypes = includeItemTypes.Length == 0 && mediaTypes.Count == 0
? new[] ? new[]
{ {
nameof(Person), BaseItemKind.Person,
nameof(Studio), BaseItemKind.Studio,
nameof(Year), BaseItemKind.Year,
nameof(MusicGenre), BaseItemKind.MusicGenre,
nameof(Genre) BaseItemKind.Genre
} }
: Array.Empty<string>(); : Array.Empty<BaseItemKind>();
var query = new InternalItemsQuery(user) var query = new InternalItemsQuery(user)
{ {
@@ -361,7 +359,7 @@ namespace Emby.Server.Implementations.Library
(ItemSortBy.SortName, SortOrder.Descending), (ItemSortBy.SortName, SortOrder.Descending),
(ItemSortBy.ProductionYear, SortOrder.Descending) (ItemSortBy.ProductionYear, SortOrder.Descending)
}, },
IsFolder = includeItemTypes.Length == 0 ? false : (bool?)null, IsFolder = includeItemTypes.Length == 0 ? false : null,
ExcludeItemTypes = excludeItemTypes, ExcludeItemTypes = excludeItemTypes,
IsVirtualItem = false, IsVirtualItem = false,
Limit = limit * 5, Limit = limit * 5,
@@ -3,6 +3,7 @@ using System.Globalization;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
@@ -81,7 +82,7 @@ namespace Emby.Server.Implementations.Library.Validators
var deadEntities = _libraryManager.GetItemList(new InternalItemsQuery var deadEntities = _libraryManager.GetItemList(new InternalItemsQuery
{ {
IncludeItemTypes = new[] { nameof(MusicArtist) }, IncludeItemTypes = new[] { BaseItemKind.MusicArtist },
IsDeadArtist = true, IsDeadArtist = true,
IsLocked = false IsLocked = false
}).Cast<MusicArtist>().ToList(); }).Cast<MusicArtist>().ToList();
@@ -1,16 +1,16 @@
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Collections.Generic; using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Collections; using MediaBrowser.Controller.Collections;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Querying;
using Jellyfin.Data.Enums;
using Microsoft.Extensions.Logging;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Querying;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Library.Validators namespace Emby.Server.Implementations.Library.Validators
{ {
@@ -64,7 +64,7 @@ namespace Emby.Server.Implementations.Library.Validators
var movies = _libraryManager.GetItemList(new InternalItemsQuery var movies = _libraryManager.GetItemList(new InternalItemsQuery
{ {
MediaTypes = new string[] { MediaType.Video }, MediaTypes = new string[] { MediaType.Video },
IncludeItemTypes = new[] { nameof(Movie) }, IncludeItemTypes = new[] { BaseItemKind.Movie },
IsVirtualItem = false, IsVirtualItem = false,
OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) }, OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) },
Parent = library, Parent = library,
@@ -108,7 +108,7 @@ namespace Emby.Server.Implementations.Library.Validators
var boxSets = _libraryManager.GetItemList(new InternalItemsQuery var boxSets = _libraryManager.GetItemList(new InternalItemsQuery
{ {
IncludeItemTypes = new[] { nameof(BoxSet) }, IncludeItemTypes = new[] { BaseItemKind.BoxSet },
CollapseBoxSetItems = false, CollapseBoxSetItems = false,
Recursive = true Recursive = true
}); });
@@ -2,6 +2,7 @@ using System;
using System.Globalization; using System.Globalization;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
@@ -91,7 +92,7 @@ namespace Emby.Server.Implementations.Library.Validators
var deadEntities = _libraryManager.GetItemList(new InternalItemsQuery var deadEntities = _libraryManager.GetItemList(new InternalItemsQuery
{ {
IncludeItemTypes = new[] { nameof(Person) }, IncludeItemTypes = new[] { BaseItemKind.Person },
IsDeadPerson = true, IsDeadPerson = true,
IsLocked = false IsLocked = false
}); });
@@ -2,6 +2,7 @@ using System;
using System.Globalization; using System.Globalization;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Persistence;
@@ -80,7 +81,7 @@ namespace Emby.Server.Implementations.Library.Validators
var deadEntities = _libraryManager.GetItemList(new InternalItemsQuery var deadEntities = _libraryManager.GetItemList(new InternalItemsQuery
{ {
IncludeItemTypes = new[] { nameof(Studio) }, IncludeItemTypes = new[] { BaseItemKind.Studio },
IsDeadStudio = true, IsDeadStudio = true,
IsLocked = false IsLocked = false
}); });
@@ -17,6 +17,7 @@ using System.Xml;
using Emby.Server.Implementations.Library; using Emby.Server.Implementations.Library;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
using Jellyfin.Data.Events; using Jellyfin.Data.Events;
using Jellyfin.Extensions;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Progress; using MediaBrowser.Common.Progress;
@@ -227,7 +228,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
foreach (var virtualFolder in virtualFolders) foreach (var virtualFolder in virtualFolders)
{ {
if (!virtualFolder.Locations.Contains(path, StringComparer.OrdinalIgnoreCase)) if (!virtualFolder.Locations.Contains(path, StringComparison.OrdinalIgnoreCase))
{ {
continue; continue;
} }
@@ -397,7 +398,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
} }
result = new EpgChannelData(channels); result = new EpgChannelData(channels);
_epgChannels.AddOrUpdate(info.Id, result, (k, v) => result); _epgChannels.AddOrUpdate(info.Id, result, (_, _) => result);
} }
return result; return result;
@@ -891,7 +892,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
throw new ArgumentNullException(nameof(tunerHostId)); throw new ArgumentNullException(nameof(tunerHostId));
} }
return info.EnabledTuners.Contains(tunerHostId, StringComparer.OrdinalIgnoreCase); return info.EnabledTuners.Contains(tunerHostId, StringComparison.OrdinalIgnoreCase);
} }
public async Task<IEnumerable<ProgramInfo>> GetProgramsAsync(string channelId, DateTime startDateUtc, DateTime endDateUtc, CancellationToken cancellationToken) public async Task<IEnumerable<ProgramInfo>> GetProgramsAsync(string channelId, DateTime startDateUtc, DateTime endDateUtc, CancellationToken cancellationToken)
@@ -1247,12 +1248,11 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
var remoteMetadata = await FetchInternetMetadata(timer, CancellationToken.None).ConfigureAwait(false); var remoteMetadata = await FetchInternetMetadata(timer, CancellationToken.None).ConfigureAwait(false);
var recordPath = GetRecordingPath(timer, remoteMetadata, out string seriesPath); var recordPath = GetRecordingPath(timer, remoteMetadata, out string seriesPath);
var recordingStatus = RecordingStatus.New;
string liveStreamId = null;
var channelItem = _liveTvManager.GetLiveTvChannel(timer, this); var channelItem = _liveTvManager.GetLiveTvChannel(timer, this);
string liveStreamId = null;
RecordingStatus recordingStatus;
try try
{ {
var allMediaSources = await _mediaSourceManager.GetPlaybackMediaSources(channelItem, null, true, false, CancellationToken.None).ConfigureAwait(false); var allMediaSources = await _mediaSourceManager.GetPlaybackMediaSources(channelItem, null, true, false, CancellationToken.None).ConfigureAwait(false);
@@ -1338,7 +1338,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
TriggerRefresh(recordPath); TriggerRefresh(recordPath);
_libraryMonitor.ReportFileSystemChangeComplete(recordPath, false); _libraryMonitor.ReportFileSystemChangeComplete(recordPath, false);
_activeRecordings.TryRemove(timer.Id, out var removed); _activeRecordings.TryRemove(timer.Id, out _);
if (recordingStatus != RecordingStatus.Completed && DateTime.UtcNow < timer.EndDate && timer.RetryCount < 10) if (recordingStatus != RecordingStatus.Completed && DateTime.UtcNow < timer.EndDate && timer.RetryCount < 10)
{ {
@@ -1778,7 +1778,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{ {
var program = string.IsNullOrWhiteSpace(timer.ProgramId) ? null : _libraryManager.GetItemList(new InternalItemsQuery var program = string.IsNullOrWhiteSpace(timer.ProgramId) ? null : _libraryManager.GetItemList(new InternalItemsQuery
{ {
IncludeItemTypes = new[] { nameof(LiveTvProgram) }, IncludeItemTypes = new[] { BaseItemKind.LiveTvProgram },
Limit = 1, Limit = 1,
ExternalId = timer.ProgramId, ExternalId = timer.ProgramId,
DtoOptions = new DtoOptions(true) DtoOptions = new DtoOptions(true)
@@ -1936,7 +1936,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
writer.WriteElementString("title", timer.EpisodeTitle); writer.WriteElementString("title", timer.EpisodeTitle);
} }
var premiereDate = item.PremiereDate ?? (!timer.IsRepeat ? DateTime.UtcNow : (DateTime?)null); var premiereDate = item.PremiereDate ?? (!timer.IsRepeat ? DateTime.UtcNow : null);
if (premiereDate.HasValue) if (premiereDate.HasValue)
{ {
@@ -2125,19 +2125,14 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
private LiveTvProgram GetProgramInfoFromCache(TimerInfo timer) private LiveTvProgram GetProgramInfoFromCache(TimerInfo timer)
{ {
return GetProgramInfoFromCache(timer.ProgramId, timer.ChannelId); return GetProgramInfoFromCache(timer.ProgramId);
}
private LiveTvProgram GetProgramInfoFromCache(string programId, string channelId)
{
return GetProgramInfoFromCache(programId);
} }
private LiveTvProgram GetProgramInfoFromCache(string channelId, DateTime startDateUtc) private LiveTvProgram GetProgramInfoFromCache(string channelId, DateTime startDateUtc)
{ {
var query = new InternalItemsQuery var query = new InternalItemsQuery
{ {
IncludeItemTypes = new string[] { nameof(LiveTvProgram) }, IncludeItemTypes = new[] { BaseItemKind.LiveTvProgram },
Limit = 1, Limit = 1,
DtoOptions = new DtoOptions(true) DtoOptions = new DtoOptions(true)
{ {
@@ -2276,7 +2271,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{ {
// Only update if not currently active - test both new timer and existing in case Id's are different // Only update if not currently active - test both new timer and existing in case Id's are different
// Id's could be different if the timer was created manually prior to series timer creation // Id's could be different if the timer was created manually prior to series timer creation
if (!_activeRecordings.TryGetValue(timer.Id, out var activeRecordingInfo) && !_activeRecordings.TryGetValue(existingTimer.Id, out activeRecordingInfo)) if (!_activeRecordings.TryGetValue(timer.Id, out _) && !_activeRecordings.TryGetValue(existingTimer.Id, out _))
{ {
UpdateExistingTimerWithNewMetadata(existingTimer, timer); UpdateExistingTimerWithNewMetadata(existingTimer, timer);
@@ -2297,17 +2292,14 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
enabledTimersForSeries.Add(existingTimer); enabledTimersForSeries.Add(existingTimer);
} }
if (updateTimerSettings) existingTimer.KeepUntil = seriesTimer.KeepUntil;
{ existingTimer.IsPostPaddingRequired = seriesTimer.IsPostPaddingRequired;
existingTimer.KeepUntil = seriesTimer.KeepUntil; existingTimer.IsPrePaddingRequired = seriesTimer.IsPrePaddingRequired;
existingTimer.IsPostPaddingRequired = seriesTimer.IsPostPaddingRequired; existingTimer.PostPaddingSeconds = seriesTimer.PostPaddingSeconds;
existingTimer.IsPrePaddingRequired = seriesTimer.IsPrePaddingRequired; existingTimer.PrePaddingSeconds = seriesTimer.PrePaddingSeconds;
existingTimer.PostPaddingSeconds = seriesTimer.PostPaddingSeconds; existingTimer.Priority = seriesTimer.Priority;
existingTimer.PrePaddingSeconds = seriesTimer.PrePaddingSeconds;
existingTimer.Priority = seriesTimer.Priority;
}
existingTimer.SeriesTimerId = seriesTimer.Id; existingTimer.SeriesTimerId = seriesTimer.Id;
_timerProvider.Update(existingTimer); _timerProvider.Update(existingTimer);
} }
} }
@@ -2332,7 +2324,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
var deletes = _timerProvider.GetAll() var deletes = _timerProvider.GetAll()
.Where(i => string.Equals(i.SeriesTimerId, seriesTimer.Id, StringComparison.OrdinalIgnoreCase)) .Where(i => string.Equals(i.SeriesTimerId, seriesTimer.Id, StringComparison.OrdinalIgnoreCase))
.Where(i => !allTimerIds.Contains(i.Id, StringComparer.OrdinalIgnoreCase) && i.StartDate > DateTime.UtcNow) .Where(i => !allTimerIds.Contains(i.Id, StringComparison.OrdinalIgnoreCase) && i.StartDate > DateTime.UtcNow)
.Where(i => deleteStatuses.Contains(i.Status)) .Where(i => deleteStatuses.Contains(i.Status))
.ToList(); .ToList();
@@ -2352,7 +2344,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
var query = new InternalItemsQuery var query = new InternalItemsQuery
{ {
IncludeItemTypes = new string[] { nameof(LiveTvProgram) }, IncludeItemTypes = new[] { BaseItemKind.LiveTvProgram },
ExternalSeriesId = seriesTimer.SeriesId, ExternalSeriesId = seriesTimer.SeriesId,
DtoOptions = new DtoOptions(true) DtoOptions = new DtoOptions(true)
{ {
@@ -2387,7 +2379,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
channel = _libraryManager.GetItemList( channel = _libraryManager.GetItemList(
new InternalItemsQuery new InternalItemsQuery
{ {
IncludeItemTypes = new string[] { nameof(LiveTvChannel) }, IncludeItemTypes = new[] { BaseItemKind.LiveTvChannel },
ItemIds = new[] { parent.ChannelId }, ItemIds = new[] { parent.ChannelId },
DtoOptions = new DtoOptions() DtoOptions = new DtoOptions()
}).FirstOrDefault() as LiveTvChannel; }).FirstOrDefault() as LiveTvChannel;
@@ -2446,7 +2438,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
channel = _libraryManager.GetItemList( channel = _libraryManager.GetItemList(
new InternalItemsQuery new InternalItemsQuery
{ {
IncludeItemTypes = new string[] { nameof(LiveTvChannel) }, IncludeItemTypes = new[] { BaseItemKind.LiveTvChannel },
ItemIds = new[] { programInfo.ChannelId }, ItemIds = new[] { programInfo.ChannelId },
DtoOptions = new DtoOptions() DtoOptions = new DtoOptions()
}).FirstOrDefault() as LiveTvChannel; }).FirstOrDefault() as LiveTvChannel;
@@ -2511,7 +2503,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
var seriesIds = _libraryManager.GetItemIds( var seriesIds = _libraryManager.GetItemIds(
new InternalItemsQuery new InternalItemsQuery
{ {
IncludeItemTypes = new[] { nameof(Series) }, IncludeItemTypes = new[] { BaseItemKind.Series },
Name = program.Name Name = program.Name
}).ToArray(); }).ToArray();
@@ -2524,7 +2516,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{ {
var result = _libraryManager.GetItemIds(new InternalItemsQuery var result = _libraryManager.GetItemIds(new InternalItemsQuery
{ {
IncludeItemTypes = new[] { nameof(Episode) }, IncludeItemTypes = new[] { BaseItemKind.Episode },
ParentIndexNumber = program.SeasonNumber.Value, ParentIndexNumber = program.SeasonNumber.Value,
IndexNumber = program.EpisodeNumber.Value, IndexNumber = program.EpisodeNumber.Value,
AncestorIds = seriesIds, AncestorIds = seriesIds,
@@ -2621,7 +2613,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
if (newDevicesOnly) if (newDevicesOnly)
{ {
discoveredDevices = discoveredDevices.Where(d => !configuredDeviceIds.Contains(d.DeviceId, StringComparer.OrdinalIgnoreCase)) discoveredDevices = discoveredDevices.Where(d => !configuredDeviceIds.Contains(d.DeviceId, StringComparison.OrdinalIgnoreCase))
.ToList(); .ToList();
} }
@@ -62,12 +62,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
using var durationToken = new CancellationTokenSource(duration); using var durationToken = new CancellationTokenSource(duration);
using var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token); using var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token);
await RecordFromFile(mediaSource, mediaSource.Path, targetFile, duration, onStarted, cancellationTokenSource.Token).ConfigureAwait(false); await RecordFromFile(mediaSource, mediaSource.Path, targetFile, onStarted, cancellationTokenSource.Token).ConfigureAwait(false);
_logger.LogInformation("Recording completed to file {0}", targetFile); _logger.LogInformation("Recording completed to file {0}", targetFile);
} }
private async Task RecordFromFile(MediaSourceInfo mediaSource, string inputFile, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken) private async Task RecordFromFile(MediaSourceInfo mediaSource, string inputFile, string targetFile, Action onStarted, CancellationToken cancellationToken)
{ {
_targetPath = targetFile; _targetPath = targetFile;
Directory.CreateDirectory(Path.GetDirectoryName(targetFile)); Directory.CreateDirectory(Path.GetDirectoryName(targetFile));
@@ -81,7 +81,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
RedirectStandardInput = true, RedirectStandardInput = true,
FileName = _mediaEncoder.EncoderPath, FileName = _mediaEncoder.EncoderPath,
Arguments = GetCommandLineArgs(mediaSource, inputFile, targetFile, duration), Arguments = GetCommandLineArgs(mediaSource, inputFile, targetFile),
WindowStyle = ProcessWindowStyle.Hidden, WindowStyle = ProcessWindowStyle.Hidden,
ErrorDialog = false ErrorDialog = false
@@ -103,7 +103,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
StartInfo = processStartInfo, StartInfo = processStartInfo,
EnableRaisingEvents = true EnableRaisingEvents = true
}; };
_process.Exited += (sender, args) => OnFfMpegProcessExited(_process); _process.Exited += (_, _) => OnFfMpegProcessExited(_process);
_process.Start(); _process.Start();
@@ -117,7 +117,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
_logger.LogInformation("ffmpeg recording process started for {0}", _targetPath); _logger.LogInformation("ffmpeg recording process started for {0}", _targetPath);
} }
private string GetCommandLineArgs(MediaSourceInfo mediaSource, string inputTempFile, string targetFile, TimeSpan duration) private string GetCommandLineArgs(MediaSourceInfo mediaSource, string inputTempFile, string targetFile)
{ {
string videoArgs; string videoArgs;
if (EncodeVideo(mediaSource)) if (EncodeVideo(mediaSource))
@@ -10,7 +10,6 @@ using System.Linq;
using System.Net; using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Net.Http.Json; using System.Net.Http.Json;
using System.Net.Http.Headers;
using System.Net.Mime; using System.Net.Mime;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Text; using System.Text;
@@ -160,8 +159,8 @@ namespace Emby.Server.Implementations.LiveTv.Listings
var programEntry = programDict[schedule.ProgramId]; var programEntry = programDict[schedule.ProgramId];
var allImages = images[imageIndex].Data; var allImages = images[imageIndex].Data;
var imagesWithText = allImages.Where(i => string.Equals(i.Text, "yes", StringComparison.OrdinalIgnoreCase)); var imagesWithText = allImages.Where(i => string.Equals(i.Text, "yes", StringComparison.OrdinalIgnoreCase)).ToList();
var imagesWithoutText = allImages.Where(i => string.Equals(i.Text, "no", StringComparison.OrdinalIgnoreCase)); var imagesWithoutText = allImages.Where(i => string.Equals(i.Text, "no", StringComparison.OrdinalIgnoreCase)).ToList();
const double DesiredAspect = 2.0 / 3; const double DesiredAspect = 2.0 / 3;
@@ -242,19 +241,19 @@ namespace Emby.Server.Implementations.LiveTv.Listings
if (programInfo.AudioProperties.Count != 0) if (programInfo.AudioProperties.Count != 0)
{ {
if (programInfo.AudioProperties.Contains("atmos", StringComparer.OrdinalIgnoreCase)) if (programInfo.AudioProperties.Contains("atmos", StringComparison.OrdinalIgnoreCase))
{ {
audioType = ProgramAudio.Atmos; audioType = ProgramAudio.Atmos;
} }
else if (programInfo.AudioProperties.Contains("dd 5.1", StringComparer.OrdinalIgnoreCase)) else if (programInfo.AudioProperties.Contains("dd 5.1", StringComparison.OrdinalIgnoreCase))
{ {
audioType = ProgramAudio.DolbyDigital; audioType = ProgramAudio.DolbyDigital;
} }
else if (programInfo.AudioProperties.Contains("dd", StringComparer.OrdinalIgnoreCase)) else if (programInfo.AudioProperties.Contains("dd", StringComparison.OrdinalIgnoreCase))
{ {
audioType = ProgramAudio.DolbyDigital; audioType = ProgramAudio.DolbyDigital;
} }
else if (programInfo.AudioProperties.Contains("stereo", StringComparer.OrdinalIgnoreCase)) else if (programInfo.AudioProperties.Contains("stereo", StringComparison.OrdinalIgnoreCase))
{ {
audioType = ProgramAudio.Stereo; audioType = ProgramAudio.Stereo;
} }
@@ -316,8 +315,8 @@ namespace Emby.Server.Implementations.LiveTv.Listings
if (programInfo.VideoProperties != null) if (programInfo.VideoProperties != null)
{ {
info.IsHD = programInfo.VideoProperties.Contains("hdtv", StringComparer.OrdinalIgnoreCase); info.IsHD = programInfo.VideoProperties.Contains("hdtv", StringComparison.OrdinalIgnoreCase);
info.Is3D = programInfo.VideoProperties.Contains("3d", StringComparer.OrdinalIgnoreCase); info.Is3D = programInfo.VideoProperties.Contains("3d", StringComparison.OrdinalIgnoreCase);
} }
if (details.ContentRating != null && details.ContentRating.Count > 0) if (details.ContentRating != null && details.ContentRating.Count > 0)
@@ -326,7 +325,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
.Replace("--", "-", StringComparison.Ordinal); .Replace("--", "-", StringComparison.Ordinal);
var invalid = new[] { "N/A", "Approved", "Not Rated", "Passed" }; var invalid = new[] { "N/A", "Approved", "Not Rated", "Passed" };
if (invalid.Contains(info.OfficialRating, StringComparer.OrdinalIgnoreCase)) if (invalid.Contains(info.OfficialRating, StringComparison.OrdinalIgnoreCase))
{ {
info.OfficialRating = null; info.OfficialRating = null;
} }
@@ -388,9 +387,9 @@ namespace Emby.Server.Implementations.LiveTv.Listings
if (details.Genres != null) if (details.Genres != null)
{ {
info.Genres = details.Genres.Where(g => !string.IsNullOrWhiteSpace(g)).ToList(); info.Genres = details.Genres.Where(g => !string.IsNullOrWhiteSpace(g)).ToList();
info.IsNews = details.Genres.Contains("news", StringComparer.OrdinalIgnoreCase); info.IsNews = details.Genres.Contains("news", StringComparison.OrdinalIgnoreCase);
if (info.Genres.Contains("children", StringComparer.OrdinalIgnoreCase)) if (info.Genres.Contains("children", StringComparison.OrdinalIgnoreCase))
{ {
info.IsKids = true; info.IsKids = true;
} }
@@ -644,7 +643,9 @@ namespace Emby.Server.Implementations.LiveTv.Listings
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
using var options = new HttpRequestMessage(HttpMethod.Post, ApiUrl + "/token"); using var options = new HttpRequestMessage(HttpMethod.Post, ApiUrl + "/token");
#pragma warning disable CA5350 // SchedulesDirect is always SHA1.
var hashedPasswordBytes = SHA1.HashData(Encoding.ASCII.GetBytes(password)); var hashedPasswordBytes = SHA1.HashData(Encoding.ASCII.GetBytes(password));
#pragma warning restore CA5350
// TODO: remove ToLower when Convert.ToHexString supports lowercase // TODO: remove ToLower when Convert.ToHexString supports lowercase
// Schedules Direct requires the hex to be lowercase // Schedules Direct requires the hex to be lowercase
string hashedPassword = Convert.ToHexString(hashedPasswordBytes).ToLowerInvariant(); string hashedPassword = Convert.ToHexString(hashedPasswordBytes).ToLowerInvariant();
@@ -821,10 +822,5 @@ namespace Emby.Server.Implementations.LiveTv.Listings
return list; return list;
} }
private static string NormalizeName(string value)
{
return value.Replace(" ", string.Empty, StringComparison.Ordinal).Replace("-", string.Empty, StringComparison.Ordinal);
}
} }
} }
@@ -190,10 +190,10 @@ namespace Emby.Server.Implementations.LiveTv.Listings
IsSeries = program.Episode != null, IsSeries = program.Episode != null,
IsRepeat = program.IsPreviouslyShown && !program.IsNew, IsRepeat = program.IsPreviouslyShown && !program.IsNew,
IsPremiere = program.Premiere != null, IsPremiere = program.Premiere != null,
IsKids = program.Categories.Any(c => info.KidsCategories.Contains(c, StringComparer.OrdinalIgnoreCase)), IsKids = program.Categories.Any(c => info.KidsCategories.Contains(c, StringComparison.OrdinalIgnoreCase)),
IsMovie = program.Categories.Any(c => info.MovieCategories.Contains(c, StringComparer.OrdinalIgnoreCase)), IsMovie = program.Categories.Any(c => info.MovieCategories.Contains(c, StringComparison.OrdinalIgnoreCase)),
IsNews = program.Categories.Any(c => info.NewsCategories.Contains(c, StringComparer.OrdinalIgnoreCase)), IsNews = program.Categories.Any(c => info.NewsCategories.Contains(c, StringComparison.OrdinalIgnoreCase)),
IsSports = program.Categories.Any(c => info.SportsCategories.Contains(c, StringComparer.OrdinalIgnoreCase)), IsSports = program.Categories.Any(c => info.SportsCategories.Contains(c, StringComparison.OrdinalIgnoreCase)),
ImageUrl = program.Icon != null && !string.IsNullOrEmpty(program.Icon.Source) ? program.Icon.Source : null, ImageUrl = program.Icon != null && !string.IsNullOrEmpty(program.Icon.Source) ? program.Icon.Source : null,
HasImage = program.Icon != null && !string.IsNullOrEmpty(program.Icon.Source), HasImage = program.Icon != null && !string.IsNullOrEmpty(program.Icon.Source),
OfficialRating = program.Rating != null && !string.IsNullOrEmpty(program.Rating.Value) ? program.Rating.Value : null, OfficialRating = program.Rating != null && !string.IsNullOrEmpty(program.Rating.Value) ? program.Rating.Value : null,
@@ -7,12 +7,12 @@ using System.Globalization;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Data.Enums;
using MediaBrowser.Common; using MediaBrowser.Common;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Model.Dto; using MediaBrowser.Model.Dto;
@@ -161,7 +161,7 @@ namespace Emby.Server.Implementations.LiveTv
{ {
var librarySeries = _libraryManager.GetItemList(new InternalItemsQuery var librarySeries = _libraryManager.GetItemList(new InternalItemsQuery
{ {
IncludeItemTypes = new string[] { nameof(Series) }, IncludeItemTypes = new[] { BaseItemKind.Series },
Name = seriesName, Name = seriesName,
Limit = 1, Limit = 1,
ImageTypes = new ImageType[] { ImageType.Thumb }, ImageTypes = new ImageType[] { ImageType.Thumb },
@@ -204,7 +204,7 @@ namespace Emby.Server.Implementations.LiveTv
var program = _libraryManager.GetItemList(new InternalItemsQuery var program = _libraryManager.GetItemList(new InternalItemsQuery
{ {
IncludeItemTypes = new string[] { nameof(LiveTvProgram) }, IncludeItemTypes = new[] { BaseItemKind.LiveTvProgram },
ExternalSeriesId = programSeriesId, ExternalSeriesId = programSeriesId,
Limit = 1, Limit = 1,
ImageTypes = new ImageType[] { ImageType.Primary }, ImageTypes = new ImageType[] { ImageType.Primary },
@@ -255,7 +255,7 @@ namespace Emby.Server.Implementations.LiveTv
{ {
var librarySeries = _libraryManager.GetItemList(new InternalItemsQuery var librarySeries = _libraryManager.GetItemList(new InternalItemsQuery
{ {
IncludeItemTypes = new string[] { nameof(Series) }, IncludeItemTypes = new[] { BaseItemKind.Series },
Name = seriesName, Name = seriesName,
Limit = 1, Limit = 1,
ImageTypes = new ImageType[] { ImageType.Thumb }, ImageTypes = new ImageType[] { ImageType.Thumb },
@@ -298,7 +298,7 @@ namespace Emby.Server.Implementations.LiveTv
var program = _libraryManager.GetItemList(new InternalItemsQuery var program = _libraryManager.GetItemList(new InternalItemsQuery
{ {
IncludeItemTypes = new string[] { nameof(Series) }, IncludeItemTypes = new[] { BaseItemKind.Series },
Name = seriesName, Name = seriesName,
Limit = 1, Limit = 1,
ImageTypes = new ImageType[] { ImageType.Primary }, ImageTypes = new ImageType[] { ImageType.Primary },
@@ -309,7 +309,7 @@ namespace Emby.Server.Implementations.LiveTv
{ {
program = _libraryManager.GetItemList(new InternalItemsQuery program = _libraryManager.GetItemList(new InternalItemsQuery
{ {
IncludeItemTypes = new string[] { nameof(LiveTvProgram) }, IncludeItemTypes = new[] { BaseItemKind.LiveTvProgram },
ExternalSeriesId = programSeriesId, ExternalSeriesId = programSeriesId,
Limit = 1, Limit = 1,
ImageTypes = new ImageType[] { ImageType.Primary }, ImageTypes = new ImageType[] { ImageType.Primary },
@@ -33,8 +33,6 @@ using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.Querying; using MediaBrowser.Model.Querying;
using MediaBrowser.Model.Tasks; using MediaBrowser.Model.Tasks;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Episode = MediaBrowser.Controller.Entities.TV.Episode;
using Movie = MediaBrowser.Controller.Entities.Movies.Movie;
namespace Emby.Server.Implementations.LiveTv namespace Emby.Server.Implementations.LiveTv
{ {
@@ -191,7 +189,7 @@ namespace Emby.Server.Implementations.LiveTv
IsKids = query.IsKids, IsKids = query.IsKids,
IsSports = query.IsSports, IsSports = query.IsSports,
IsSeries = query.IsSeries, IsSeries = query.IsSeries,
IncludeItemTypes = new[] { nameof(LiveTvChannel) }, IncludeItemTypes = new[] { BaseItemKind.LiveTvChannel },
TopParentIds = new[] { topFolder.Id }, TopParentIds = new[] { topFolder.Id },
IsFavorite = query.IsFavorite, IsFavorite = query.IsFavorite,
IsLiked = query.IsLiked, IsLiked = query.IsLiked,
@@ -209,7 +207,7 @@ namespace Emby.Server.Implementations.LiveTv
orderBy.Insert(0, (ItemSortBy.IsFavoriteOrLiked, SortOrder.Descending)); orderBy.Insert(0, (ItemSortBy.IsFavoriteOrLiked, SortOrder.Descending));
} }
if (!internalQuery.OrderBy.Any(i => string.Equals(i.Item1, ItemSortBy.SortName, StringComparison.OrdinalIgnoreCase))) if (!internalQuery.OrderBy.Any(i => string.Equals(i.OrderBy, ItemSortBy.SortName, StringComparison.OrdinalIgnoreCase)))
{ {
orderBy.Add((ItemSortBy.SortName, SortOrder.Ascending)); orderBy.Add((ItemSortBy.SortName, SortOrder.Ascending));
} }
@@ -522,7 +520,7 @@ namespace Emby.Server.Implementations.LiveTv
return item; return item;
} }
private (LiveTvProgram item, bool isNew, bool isUpdated) GetProgram(ProgramInfo info, Dictionary<Guid, LiveTvProgram> allExistingPrograms, LiveTvChannel channel) private (LiveTvProgram Item, bool IsNew, bool IsUpdated) GetProgram(ProgramInfo info, Dictionary<Guid, LiveTvProgram> allExistingPrograms, LiveTvChannel channel)
{ {
var id = _tvDtoService.GetInternalProgramId(info.Id); var id = _tvDtoService.GetInternalProgramId(info.Id);
@@ -781,9 +779,9 @@ namespace Emby.Server.Implementations.LiveTv
var dto = _dtoService.GetBaseItemDto(program, new DtoOptions(), user); var dto = _dtoService.GetBaseItemDto(program, new DtoOptions(), user);
var list = new List<Tuple<BaseItemDto, string, string>> var list = new List<(BaseItemDto ItemDto, string ExternalId, string ExternalSeriesId)>
{ {
new Tuple<BaseItemDto, string, string>(dto, program.ExternalId, program.ExternalSeriesId) (dto, program.ExternalId, program.ExternalSeriesId)
}; };
await AddRecordingInfo(list, cancellationToken).ConfigureAwait(false); await AddRecordingInfo(list, cancellationToken).ConfigureAwait(false);
@@ -810,7 +808,7 @@ namespace Emby.Server.Implementations.LiveTv
var internalQuery = new InternalItemsQuery(user) var internalQuery = new InternalItemsQuery(user)
{ {
IncludeItemTypes = new[] { nameof(LiveTvProgram) }, IncludeItemTypes = new[] { BaseItemKind.LiveTvProgram },
MinEndDate = query.MinEndDate, MinEndDate = query.MinEndDate,
MinStartDate = query.MinStartDate, MinStartDate = query.MinStartDate,
MaxEndDate = query.MaxEndDate, MaxEndDate = query.MaxEndDate,
@@ -874,7 +872,7 @@ namespace Emby.Server.Implementations.LiveTv
var internalQuery = new InternalItemsQuery(user) var internalQuery = new InternalItemsQuery(user)
{ {
IncludeItemTypes = new[] { nameof(LiveTvProgram) }, IncludeItemTypes = new[] { BaseItemKind.LiveTvProgram },
IsAiring = query.IsAiring, IsAiring = query.IsAiring,
HasAired = query.HasAired, HasAired = query.HasAired,
IsNews = query.IsNews, IsNews = query.IsNews,
@@ -978,16 +976,16 @@ namespace Emby.Server.Implementations.LiveTv
return score; return score;
} }
private async Task AddRecordingInfo(IEnumerable<Tuple<BaseItemDto, string, string>> programs, CancellationToken cancellationToken) private async Task AddRecordingInfo(IEnumerable<(BaseItemDto ItemDto, string ExternalId, string ExternalSeriesId)> programs, CancellationToken cancellationToken)
{ {
IReadOnlyList<TimerInfo> timerList = null; IReadOnlyList<TimerInfo> timerList = null;
IReadOnlyList<SeriesTimerInfo> seriesTimerList = null; IReadOnlyList<SeriesTimerInfo> seriesTimerList = null;
foreach (var programTuple in programs) foreach (var programTuple in programs)
{ {
var program = programTuple.Item1; var program = programTuple.ItemDto;
var externalProgramId = programTuple.Item2; var externalProgramId = programTuple.ExternalId;
string externalSeriesId = programTuple.Item3; string externalSeriesId = programTuple.ExternalSeriesId;
timerList ??= (await GetTimersInternal(new TimerQuery(), cancellationToken).ConfigureAwait(false)).Items; timerList ??= (await GetTimersInternal(new TimerQuery(), cancellationToken).ConfigureAwait(false)).Items;
@@ -1085,8 +1083,8 @@ namespace Emby.Server.Implementations.LiveTv
if (cleanDatabase) if (cleanDatabase)
{ {
CleanDatabaseInternal(newChannelIdList.ToArray(), new[] { nameof(LiveTvChannel) }, progress, cancellationToken); CleanDatabaseInternal(newChannelIdList.ToArray(), new[] { BaseItemKind.LiveTvChannel }, progress, cancellationToken);
CleanDatabaseInternal(newProgramIdList.ToArray(), new[] { nameof(LiveTvProgram) }, progress, cancellationToken); CleanDatabaseInternal(newProgramIdList.ToArray(), new[] { BaseItemKind.LiveTvProgram }, progress, cancellationToken);
} }
var coreService = _services.OfType<EmbyTV.EmbyTV>().FirstOrDefault(); var coreService = _services.OfType<EmbyTV.EmbyTV>().FirstOrDefault();
@@ -1177,7 +1175,7 @@ namespace Emby.Server.Implementations.LiveTv
var existingPrograms = _libraryManager.GetItemList(new InternalItemsQuery var existingPrograms = _libraryManager.GetItemList(new InternalItemsQuery
{ {
IncludeItemTypes = new string[] { nameof(LiveTvProgram) }, IncludeItemTypes = new[] { BaseItemKind.LiveTvProgram },
ChannelIds = new Guid[] { currentChannel.Id }, ChannelIds = new Guid[] { currentChannel.Id },
DtoOptions = new DtoOptions(true) DtoOptions = new DtoOptions(true)
}).Cast<LiveTvProgram>().ToDictionary(i => i.Id); }).Cast<LiveTvProgram>().ToDictionary(i => i.Id);
@@ -1188,13 +1186,13 @@ namespace Emby.Server.Implementations.LiveTv
foreach (var program in channelPrograms) foreach (var program in channelPrograms)
{ {
var programTuple = GetProgram(program, existingPrograms, currentChannel); var programTuple = GetProgram(program, existingPrograms, currentChannel);
var programItem = programTuple.item; var programItem = programTuple.Item;
if (programTuple.isNew) if (programTuple.IsNew)
{ {
newPrograms.Add(programItem); newPrograms.Add(programItem);
} }
else if (programTuple.isUpdated) else if (programTuple.IsUpdated)
{ {
updatedPrograms.Add(programItem); updatedPrograms.Add(programItem);
} }
@@ -1261,7 +1259,7 @@ namespace Emby.Server.Implementations.LiveTv
return new Tuple<List<Guid>, List<Guid>>(channels, programs); return new Tuple<List<Guid>, List<Guid>>(channels, programs);
} }
private void CleanDatabaseInternal(Guid[] currentIdList, string[] validTypes, IProgress<double> progress, CancellationToken cancellationToken) private void CleanDatabaseInternal(Guid[] currentIdList, BaseItemKind[] validTypes, IProgress<double> progress, CancellationToken cancellationToken)
{ {
var list = _itemRepo.GetItemIdsList(new InternalItemsQuery var list = _itemRepo.GetItemIdsList(new InternalItemsQuery
{ {
@@ -1328,25 +1326,25 @@ namespace Emby.Server.Implementations.LiveTv
.Select(i => i.Id) .Select(i => i.Id)
.ToList(); .ToList();
var excludeItemTypes = new List<string>(); var excludeItemTypes = new List<BaseItemKind>();
if (folderIds.Count == 0) if (folderIds.Count == 0)
{ {
return new QueryResult<BaseItem>(); return new QueryResult<BaseItem>();
} }
var includeItemTypes = new List<string>(); var includeItemTypes = new List<BaseItemKind>();
var genres = new List<string>(); var genres = new List<string>();
if (query.IsMovie.HasValue) if (query.IsMovie.HasValue)
{ {
if (query.IsMovie.Value) if (query.IsMovie.Value)
{ {
includeItemTypes.Add(nameof(Movie)); includeItemTypes.Add(BaseItemKind.Movie);
} }
else else
{ {
excludeItemTypes.Add(nameof(Movie)); excludeItemTypes.Add(BaseItemKind.Movie);
} }
} }
@@ -1354,11 +1352,11 @@ namespace Emby.Server.Implementations.LiveTv
{ {
if (query.IsSeries.Value) if (query.IsSeries.Value)
{ {
includeItemTypes.Add(nameof(Episode)); includeItemTypes.Add(BaseItemKind.Episode);
} }
else else
{ {
excludeItemTypes.Add(nameof(Episode)); excludeItemTypes.Add(BaseItemKind.Episode);
} }
} }
@@ -1425,9 +1423,9 @@ namespace Emby.Server.Implementations.LiveTv
return result; return result;
} }
public Task AddInfoToProgramDto(IReadOnlyCollection<(BaseItem, BaseItemDto)> programs, IReadOnlyList<ItemFields> fields, User user = null) public Task AddInfoToProgramDto(IReadOnlyCollection<(BaseItem Item, BaseItemDto ItemDto)> programs, IReadOnlyList<ItemFields> fields, User user = null)
{ {
var programTuples = new List<Tuple<BaseItemDto, string, string>>(); var programTuples = new List<(BaseItemDto Dto, string ExternalId, string ExternalSeriesId)>();
var hasChannelImage = fields.Contains(ItemFields.ChannelImage); var hasChannelImage = fields.Contains(ItemFields.ChannelImage);
var hasChannelInfo = fields.Contains(ItemFields.ChannelInfo); var hasChannelInfo = fields.Contains(ItemFields.ChannelInfo);
@@ -1463,7 +1461,7 @@ namespace Emby.Server.Implementations.LiveTv
} }
} }
programTuples.Add(new Tuple<BaseItemDto, string, string>(dto, program.ExternalId, program.ExternalSeriesId)); programTuples.Add((dto, program.ExternalId, program.ExternalSeriesId));
} }
return AddRecordingInfo(programTuples, CancellationToken.None); return AddRecordingInfo(programTuples, CancellationToken.None);
@@ -1870,15 +1868,15 @@ namespace Emby.Server.Implementations.LiveTv
return _libraryManager.GetItemById(internalChannelId); return _libraryManager.GetItemById(internalChannelId);
} }
public void AddChannelInfo(IReadOnlyCollection<(BaseItemDto, LiveTvChannel)> items, DtoOptions options, User user) public void AddChannelInfo(IReadOnlyCollection<(BaseItemDto ItemDto, LiveTvChannel Channel)> items, DtoOptions options, User user)
{ {
var now = DateTime.UtcNow; var now = DateTime.UtcNow;
var channelIds = items.Select(i => i.Item2.Id).Distinct().ToArray(); var channelIds = items.Select(i => i.Channel.Id).Distinct().ToArray();
var programs = options.AddCurrentProgram ? _libraryManager.GetItemList(new InternalItemsQuery(user) var programs = options.AddCurrentProgram ? _libraryManager.GetItemList(new InternalItemsQuery(user)
{ {
IncludeItemTypes = new[] { nameof(LiveTvProgram) }, IncludeItemTypes = new[] { BaseItemKind.LiveTvProgram },
ChannelIds = channelIds, ChannelIds = channelIds,
MaxStartDate = now, MaxStartDate = now,
MinEndDate = now, MinEndDate = now,
@@ -1895,11 +1893,8 @@ namespace Emby.Server.Implementations.LiveTv
var addCurrentProgram = options.AddCurrentProgram; var addCurrentProgram = options.AddCurrentProgram;
foreach (var tuple in items) foreach (var (dto, channel) in items)
{ {
var dto = tuple.Item1;
var channel = tuple.Item2;
dto.Number = channel.Number; dto.Number = channel.Number;
dto.ChannelNumber = channel.Number; dto.ChannelNumber = channel.Number;
dto.ChannelType = channel.ChannelType; dto.ChannelType = channel.ChannelType;
@@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
_profile = profile; _profile = profile;
} }
public IEnumerable<(string, string)> GetCommands() public IEnumerable<(string CommandName, string CommandValue)> GetCommands()
{ {
if (!string.IsNullOrEmpty(_channel)) if (!string.IsNullOrEmpty(_channel))
{ {

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